PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/start/main.go

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