PageRenderTime 64ms CodeModel.GetById 9ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 0ms

/src/start/main.go

https://code.google.com/p/godag/
Go | 545 lines | 410 code | 78 blank | 57 comment | 77 complexity | 0467ffb74789ef8129bceb9ee9f7c124 MD5 | raw file
  1//  Copyright Š 2009 bjarneh
  2//
  3//  This program is free software: you can redistribute it and/or modify
  4//  it under the terms of the GNU General Public License as published by
  5//  the Free Software Foundation, either version 3 of the License, or
  6//  (at your option) any later version.
  7//
  8//  This program is distributed in the hope that it will be useful,
  9//  but WITHOUT ANY WARRANTY; without even the implied warranty of
 10//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11//  GNU General Public License for more details.
 12//
 13//  You should have received a copy of the GNU General Public License
 14//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 15
 16package main
 17
 18import (
 19    "cmplr/compiler"
 20    "cmplr/dag"
 21    "cmplr/gdmake"
 22    "fmt"
 23    "log"
 24    "os"
 25    "parse/gopt"
 26    "path/filepath"
 27    "runtime"
 28    "strings"
 29    "utilz/global"
 30    "utilz/handy"
 31    "utilz/say"
 32    "utilz/timer"
 33    "utilz/walker"
 34)
 35
 36// option parser object (struct)
 37var getopt *gopt.GetOpt
 38
 39// list of files to compile
 40var files []string
 41
 42// libraries other than $GOROOT/pkg/PLATFORM
 43var includes []string = make([]string, 0)
 44
 45// source root
 46var srcdir string = "src"
 47
 48// keys for the bool options
 49var bools = []string{
 50    "-help",
 51    "-clean",
 52    "-static",
 53    "-version",
 54    "-sort",
 55    "-print",
 56    "-dryrun",
 57    "-test",
 58    "-list",
 59    "-verbose",
 60    "-fmt",
 61    "-all",
 62    "-quiet",
 63    "-tab",
 64    "-external",
 65    "-update-external",
 66    // add missing test options + alias
 67    "-test.short",
 68    "-test.v",
 69    "-strip",
 70}
 71
 72// keys for the string options
 73// note: -I is handled seperately
 74var strs = []string{
 75    "-dot",
 76    "-tabwidth",
 77    "-rewrite",
 78    "-output",
 79    "-bench",
 80    "-match",
 81    "-test-bin",
 82    "-lib",
 83    "-main",
 84    "-backend",
 85    "-gdmk",
 86    "-mkcomplete",
 87    // add missing test options + alias
 88    "-test.bench",
 89    "-test.benchtime",
 90    "-test.cpu",
 91    "-test.cpuprofile",
 92    "-test.memprofile",
 93    "-test.memprofilerate",
 94    "-test.timeout",
 95    "-test.parallel",
 96}
 97
 98func init() {
 99
100    // initialize option parser
101    getopt = gopt.New()
102
103    // add all options (bool/string)
104    getopt.BoolOption("-h -help --help help")
105    getopt.BoolOption("-c -clean --clean clean")
106    getopt.BoolOption("-S -static --static")
107    getopt.BoolOption("-v -version --version version")
108    getopt.BoolOption("-s -sort --sort sort")
109    getopt.BoolOption("-p -print --print print")
110    getopt.BoolOption("-d -dryrun --dryrun dryrun")
111    getopt.BoolOption("-t -test --test test")
112    getopt.BoolOption("-l -list --list list")
113    getopt.BoolOption("-q -quiet --quiet")
114    getopt.BoolOption("-V -verbose --verbose")
115    getopt.BoolOption("-f -fmt --fmt fmt")
116    getopt.BoolOption("-T -tab --tab")
117    getopt.BoolOption("-a -all --all")
118    getopt.BoolOption("-y -strip --strip strip")
119    getopt.BoolOption("-e -external --external")
120    getopt.BoolOption("-u -updatex --updatex "+
121                      "-update-external --update-external")
122    getopt.StringOption("-I -I=")
123    getopt.StringOption("-mkcomplete")
124    getopt.StringOptionFancy("-D --dot")
125    getopt.StringOptionFancy("-L --lib")
126    getopt.StringOptionFancy("-g --gdmk")
127    getopt.StringOptionFancy("-w --tabwidth")
128    getopt.StringOptionFancy("-r --rewrite")
129    getopt.StringOptionFancy("-o --output")
130    getopt.StringOptionFancy("-M --main")
131    getopt.StringOptionFancy("-b --bench")
132    getopt.StringOptionFancy("-m --match")
133    getopt.StringOptionFancy("--test-bin")
134    getopt.StringOptionFancy("-B --backend")
135
136    // new test options and aliases
137    getopt.BoolOption("-test.short --test.short")
138    getopt.BoolOption("-test.v --test.v")
139    getopt.StringOptionFancy("--test.bench")
140    getopt.StringOptionFancy("--test.benchtime")
141    getopt.StringOptionFancy("--test.cpu")
142    getopt.StringOptionFancy("--test.cpuprofile")
143    getopt.StringOptionFancy("--test.memprofile")
144    getopt.StringOptionFancy("--test.memprofilerate")
145    getopt.StringOptionFancy("--test.timeout")
146    getopt.StringOptionFancy("--test.parallel")
147
148    // override IncludeFile to make walker pick up only .go files
149    walker.IncludeFile = noTestFilesFilter
150
151    // override IncludeDir to make walker ignore 'hidden' directories
152    walker.IncludeDir = func(s string) bool {
153        _, dirname := filepath.Split(s)
154        return dirname[0] != '.'
155    }
156
157    for _, bkey := range bools {
158        global.SetBool(bkey, false)
159    }
160
161    for _, skey := range strs {
162        global.SetString(skey, "")
163    }
164
165    // Testing on Windows requires .exe ending
166    goos := handy.GOOS()
167
168    if goos == "windows" {
169        global.SetString("-test-bin", "gdtest.exe")
170    } else {
171        global.SetString("-test-bin", "gdtest")
172    }
173
174    global.SetString("-backend", runtime.Compiler)
175    global.SetString("-I", "")
176
177}
178
179// utility func for walker: *.go unless start = '_' || end = _test.go
180func noTestFilesFilter(s string) bool {
181    return strings.HasSuffix(s, ".go") &&
182        !strings.HasSuffix(s, "_test.go") &&
183        !strings.HasPrefix(filepath.Base(s), "_")
184}
185
186// utility func for walker: *.go unless start = '_'
187func allGoFilesFilter(s string) bool {
188    return strings.HasSuffix(s, ".go") &&
189        !strings.HasPrefix(filepath.Base(s), "_")
190}
191
192func reportTime() {
193    timer.Stop("everything")
194    delta, _ := timer.Delta("everything")
195    say.Printf("time used: %s\n", timer.Nano2Time(delta))
196}
197
198func main() {
199
200    var (
201        ok, up2date bool
202        e           error
203        argv, args  []string
204        config      [4]string
205    )
206
207    timer.Start("everything")
208    defer reportTime()
209
210    // possible config locations
211    config[0] = filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "godag", "gdrc")
212    config[1] = filepath.Join(os.Getenv("HOME"), ".config", "godag", "gdrc")
213    config[2] = filepath.Join(os.Getenv("HOME"), ".gdrc")
214    config[3] = filepath.Join(os.Getenv("PWD"), ".gdrc")
215
216    for _, conf := range config {
217
218        argv, ok = handy.ConfigToArgv(conf)
219
220        if ok {
221            args = parseArgv(argv)
222            if len(args) > 0 {
223                log.Print("[WARNING] non-option arguments in config file\n")
224            }
225        }
226    }
227
228    // small gorun version if single go-file is given
229    if len(os.Args) > 1 && strings.HasSuffix(os.Args[1], ".go") && handy.IsFile(os.Args[1]) {
230        say.Mute() // be silent unless error here
231        single, name := dag.ParseSingle(os.Args[1])
232        compiler.InitBackend()
233        compiler.CreateArgv(single)
234        up2date = compiler.Compile(single)
235        if handy.GOOS() == "windows" {
236            name = name + ".exe"
237        }
238        compiler.ForkLink(name, single, nil, up2date)
239        args = os.Args[1:]
240        args[0] = name
241        handy.StdExecve(args, true)
242        os.Exit(0)
243    }
244
245
246    // command line arguments overrides/appends config
247    args = parseArgv(os.Args[1:])
248
249    mkcomplete := global.GetString("-mkcomplete")
250    if mkcomplete != "" {
251        targets := dag.GetMakeTargets(mkcomplete)
252        for _, t := range targets {
253            fmt.Println(t)
254        }
255        os.Exit(0)
256    }
257
258    if len(args) > 0 {
259        if len(args) > 1 {
260            log.Print("[WARNING] len(input directories) > 1\n")
261        }
262        srcdir = args[0]
263        if srcdir == "." {
264            srcdir, e = os.Getwd()
265            if e != nil {
266                log.Fatal("[ERROR] can't find working directory\n")
267            }
268        }
269    }
270
271    // expand variables in includes
272    for i := 0; i < len(includes); i++ {
273        includes[i] = os.ExpandEnv(includes[i])
274    }
275
276    // expand variables in -lib
277    global.SetString("-lib", os.ExpandEnv(global.GetString("-lib")))
278
279    // expand variables in -output
280    global.SetString("-output", os.ExpandEnv(global.GetString("-output")))
281
282    if global.GetBool("-list") {
283        printListing()
284        os.Exit(0)
285    }
286
287    if global.GetBool("-help") {
288        printHelp()
289        os.Exit(0)
290    }
291
292    if global.GetBool("-version") {
293        printVersion()
294        os.Exit(0)
295    }
296
297    if len(args) == 0 {
298        // give nice feedback if missing input dir
299        if !handy.IsDir("src") {
300            fmt.Printf("usage: gd [OPTIONS] src-directory\n")
301            os.Exit(1)
302        }
303    }
304
305    if global.GetBool("-quiet") {
306        say.Mute()
307    }
308
309    handy.DirOrExit(srcdir)
310    files = walker.PathWalk(filepath.Clean(srcdir))
311
312    // gofmt on all files gathered
313    if global.GetBool("-fmt") {
314        compiler.FormatFiles(files)
315        os.Exit(0)
316    }
317
318    // parse the source code, look for dependencies
319    dgrph := dag.New()
320    dgrph.Parse(srcdir, files)
321
322    // print collected dependency info
323    if global.GetBool("-print") {
324        dgrph.PrintInfo()
325        os.Exit(0)
326    }
327
328    // draw graphviz dot graph
329    if global.GetString("-dot") != "" {
330        dgrph.MakeDotGraph(global.GetString("-dot"))
331        os.Exit(0)
332    }
333
334    // build  all external dependencies
335    if global.GetBool("-external") {
336       // update external dependencies
337       if global.GetBool("-update-external") {
338        dgrph.External(true)
339       } else {
340        dgrph.External(false)
341       }
342        os.Exit(0)
343    }
344
345    // sort graph based on dependencies
346    dgrph.GraphBuilder()
347    sorted := dgrph.Topsort()
348
349    // clean only what we possibly could have generated…
350    if global.GetBool("-clean") {
351        compiler.DeleteObjects(srcdir, sorted)
352        os.Exit(0)
353    }
354
355    // print packages sorted
356    if global.GetBool("-sort") {
357        for i := 0; i < len(sorted); i++ {
358            fmt.Printf("%s\n", sorted[i].Name)
359        }
360        os.Exit(0)
361    }
362
363    // compile argv
364    compiler.Init(srcdir, includes)
365    if global.GetString("-lib") != "" {
366        compiler.CreateLibArgv(sorted)
367    } else {
368        compiler.CreateArgv(sorted)
369    }
370
371    // gdmk
372    if global.GetString("-gdmk") != "" {
373        gdmake.Make(global.GetString("-gdmk"), sorted, dgrph.Alien().Slice())
374        os.Exit(0)
375    }
376
377    // compile; up2date == true => 0 packages modified
378    if global.GetBool("-dryrun") {
379        compiler.Dryrun(sorted)
380    } else {
381        up2date = compiler.Compile(sorted) // updated parallel
382    }
383
384    // test
385    if global.GetBool("-test") {
386        os.Setenv("SRCROOT", srcdir)
387        testMain, testDir, testLib := dgrph.MakeMainTest(srcdir)
388        if global.GetString("-lib") != "" {
389            compiler.CreateLibArgv(testMain)
390        } else {
391            compiler.CreateArgv(testMain)
392        }
393        if !global.GetBool("-dryrun") {
394            compiler.Compile(testMain)
395        }
396        switch global.GetString("-backend") {
397        case "gc", "express":
398            compiler.ForkLink(global.GetString("-test-bin"), testMain, nil, false)
399        case "gccgo", "gcc":
400            compiler.ForkLink(global.GetString("-test-bin"), testMain, sorted, false)
401        default:
402            log.Fatalf("[ERROR] '%s' unknown back-end\n", global.GetString("-backend"))
403        }
404        compiler.DeletePackages(testMain)
405        handy.Delete(testDir, false)
406        if testLib != "" {
407            handy.Delete(testLib, false)
408        }
409        testArgv := compiler.CreateTestArgv()
410        if global.GetBool("-dryrun") {
411            testArgv[0] = filepath.Base(testArgv[0])
412            say.Printf("%s\n", strings.Join(testArgv, " "))
413        } else {
414            say.Printf("testing  : ")
415            if global.GetBool("-verbose") || global.GetBool("-test.v") {
416                say.Printf("\n")
417            }
418            ok = handy.StdExecve(testArgv, false)
419            handy.Delete(global.GetString("-test-bin"), false)
420            if !ok {
421                os.Exit(1)
422            }
423        }
424
425        // if packages contain both test-files and regular files
426        // test-files should not be part of the objects, i.e. init
427        // functions in test-packages can cause unexpected behaviour
428
429        if compiler.ReCompile(sorted) {
430            say.Printf("recompile: --tests\n")
431            compiler.Compile(sorted)
432        }
433
434    }
435
436    // link if ! up2date
437    if global.GetString("-output") != "" {
438        compiler.ForkLink(global.GetString("-output"), sorted, nil, up2date)
439    } else if global.GetBool("-all") {
440        compiler.ForkLinkAll(sorted, up2date)
441    }
442
443}
444
445func parseArgv(argv []string) (args []string) {
446
447    args = getopt.Parse(argv)
448
449    for _, bkey := range bools {
450        if getopt.IsSet(bkey) {
451            global.SetBool(bkey, true)
452        }
453    }
454
455    for _, skey := range strs {
456        if getopt.IsSet(skey) {
457            global.SetString(skey, getopt.Get(skey))
458        }
459    }
460
461    if getopt.IsSet("-test") || getopt.IsSet("-fmt") || getopt.IsSet("-clean") {
462        // override IncludeFile to make walker pick _test.go files
463        walker.IncludeFile = allGoFilesFilter
464    }
465
466    if getopt.IsSet("-gdmk") {
467        global.SetString("-lib", "_obj")
468        // gdmk does not support testing
469        walker.IncludeFile = noTestFilesFilter
470    }
471
472    if getopt.IsSet("-I") {
473        includes = append(includes, getopt.GetMultiple("-I")...)
474    }
475
476    getopt.Reset()
477
478    return args
479}
480
481func printHelp() {
482    var helpMSG string = `
483  Godag is a compiler front-end for golang,
484  its main purpose is to help build projects
485  which are pure Go-code without Makefiles.
486  Hopefully it simplifies testing as well.
487
488  usage: gd [OPTIONS] src-directory
489
490  options:
491
492  -h --help            print this message and quit
493  -v --version         print version and quit
494  -l --list            list option values and quit
495  -p --print           print package info collected
496  -s --sort            print legal compile order
497  -o --output          link main package -> output
498  -S --static          statically link binary
499  -y --strip           strip symbols from executable
500  -g --gdmk            create a go makefile for project
501  -d --dryrun          print what gd would do (stdout)
502  -c --clean           delete generated object code
503  -q --quiet           silent, print only errors
504  -L --lib             write objects to other dir (!src)
505  -M --main            regex to select main package
506  -a --all             link main pkgs to bin/nameOfMainDir
507  -D --dot             create a graphviz dot file
508  -I                   import package directories
509  -t --test            run all unit-tests
510  -m --match           regex to select unit-tests
511  -b --bench           regex to select benchmarks
512  -V --verbose         verbose unit-test and go install
513  --test-bin           name of test-binary (default: gdtest)
514  --test.*             any valid gotest option
515  -f --fmt             run gofmt on src and exit
516  -r --rewrite         pass rewrite rule to gofmt
517  -T --tab             pass -tabs=true to gofmt
518  -w --tabwidth        pass -tabwidth to gofmt (default: 4)
519  -e --external        go install all external dependencies
520  -u --updatex         go install -u all external dependencies
521  -B --backend         [gc,gccgo,express] (default: gc)
522    `
523
524    fmt.Println(helpMSG)
525}
526
527func printVersion() {
528    fmt.Println("godag 0.4.0 (release-branch.go1.1)")
529}
530
531func printListing() {
532
533    fmt.Println("\n Listing of options and their content:\n")
534    defer fmt.Println("")
535
536    for i := 0; i < len(bools); i++ {
537        fmt.Printf(" %-20s  =>    %v\n", bools[i], global.GetBool(bools[i]))
538    }
539
540    for i := 0; i < len(strs); i++ {
541        fmt.Printf(" %-20s  =>    %v\n", strs[i], global.GetString(strs[i]))
542    }
543
544    fmt.Printf(" %-20s  =>    %v\n", "-lib", includes)
545}