/src/start/main.go
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}