build.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. // Build builds code as directed by json files.
  2. // We slurp in the JSON, and recursively process includes.
  3. // At the end, we issue a single cc command for all the files.
  4. // Compilers are fast.
  5. //
  6. // ENVIRONMENT
  7. //
  8. // Needed: CC, HARVEY, ARCH
  9. //
  10. // HARVEY should point to a harvey root.
  11. // A best-effort to autodetect the harvey root is made if not explicitly set.
  12. //
  13. // Optional: AR, LD, RANLIB, STRIP, SH, TOOLPREFIX
  14. //
  15. // These all control how the needed tools are found.
  16. //
  17. package main
  18. import (
  19. "encoding/json"
  20. "flag"
  21. "fmt"
  22. "io/ioutil"
  23. "log"
  24. "os"
  25. "os/exec"
  26. "path"
  27. "path/filepath"
  28. "regexp"
  29. "strings"
  30. )
  31. type kernconfig struct {
  32. Code []string
  33. Dev []string
  34. Ip []string
  35. Link []string
  36. Sd []string
  37. Uart []string
  38. VGA []string
  39. }
  40. type kernel struct {
  41. Systab string
  42. Config kernconfig
  43. Ramfiles map[string]string
  44. }
  45. type build struct {
  46. // jsons is unexported so can not be set in a .json file
  47. jsons map[string]bool
  48. path string
  49. name string
  50. // Projects name a whole subproject which is built independently of
  51. // this one. We'll need to be able to use environment variables at some point.
  52. Projects []string
  53. Pre []string
  54. Post []string
  55. Cflags []string
  56. Oflags []string
  57. Include []string
  58. SourceFiles []string
  59. ObjectFiles []string
  60. Libs []string
  61. Env []string
  62. SrcDeps []string
  63. // cmd's
  64. SourceFilesCmd []string
  65. // Targets.
  66. Program string
  67. Library string
  68. Install string // where to place the resulting binary/lib
  69. Kernel *kernel
  70. }
  71. type buildfile map[string]build
  72. // UnmarshalJSON works like the stdlib unmarshal would, except it adjusts all
  73. // paths.
  74. func (bf *buildfile) UnmarshalJSON(s []byte) error {
  75. r := make(map[string]build)
  76. if err := json.Unmarshal(s, &r); err != nil {
  77. return err
  78. }
  79. for k, b := range r {
  80. // we're getting a copy of the struct, remember.
  81. b.jsons = make(map[string]bool)
  82. b.Projects = adjust(b.Projects)
  83. b.Libs = adjust(b.Libs)
  84. b.SrcDeps = adjust(b.SrcDeps)
  85. b.Cflags = adjust(b.Cflags)
  86. b.Oflags = adjust(b.Oflags)
  87. b.SourceFiles = adjust(b.SourceFiles)
  88. b.SourceFilesCmd = adjust(b.SourceFilesCmd)
  89. b.ObjectFiles = adjust(b.ObjectFiles)
  90. b.Include = adjust(b.Include)
  91. b.Install = fromRoot(b.Install)
  92. for i, e := range b.Env {
  93. b.Env[i] = os.ExpandEnv(e)
  94. }
  95. r[k] = b
  96. }
  97. *bf = r
  98. return nil
  99. }
  100. var (
  101. cwd string
  102. harvey string
  103. regexpAll = []*regexp.Regexp{regexp.MustCompile(".")}
  104. // findTools looks at all env vars and absolutizes these paths
  105. // also respects TOOLPREFIX
  106. tools = map[string]string{
  107. "cc": "gcc",
  108. "ar": "ar",
  109. "ld": "ld",
  110. "ranlib": "ranlib",
  111. "strip": "strip",
  112. }
  113. arch = map[string]bool{
  114. "amd64": true,
  115. "riscv": true,
  116. "aarch64": true,
  117. }
  118. debugPrint = flag.Bool("debug", false, "enable debug prints")
  119. depends = flag.Bool("D", false, "Do dependency checking")
  120. shellhack = flag.Bool("shellhack", false, "spawn every command in a shell (forced on if LD_PRELOAD is set)")
  121. )
  122. func debug(fmt string, s ...interface{}) {
  123. if *debugPrint {
  124. log.Printf(fmt, s...)
  125. }
  126. }
  127. // fail with message, if err is not nil
  128. func failOn(err error) {
  129. if err != nil {
  130. log.Fatalf("%v", err)
  131. }
  132. }
  133. func adjust(s []string) []string {
  134. for i, v := range s {
  135. s[i] = fromRoot(v)
  136. }
  137. return s
  138. }
  139. // return the given absolute path as an absolute path rooted at the harvey tree.
  140. func fromRoot(p string) string {
  141. p = os.ExpandEnv(p)
  142. if path.IsAbs(p) {
  143. return path.Join(harvey, p)
  144. }
  145. return p
  146. }
  147. func include(f string, b *build) {
  148. if b.jsons[f] {
  149. return
  150. }
  151. b.jsons[f] = true
  152. log.Printf("Including %v", f)
  153. d, err := ioutil.ReadFile(f)
  154. failOn(err)
  155. var builds buildfile
  156. failOn(json.Unmarshal(d, &builds))
  157. for n, build := range builds {
  158. log.Printf("Merging %v", n)
  159. b.Cflags = append(b.Cflags, build.Cflags...)
  160. b.Oflags = append(b.Oflags, build.Oflags...)
  161. b.Pre = append(b.Pre, build.Pre...)
  162. b.Post = append(b.Post, build.Post...)
  163. b.Libs = append(b.Libs, build.Libs...)
  164. b.Projects = append(b.Projects, build.Projects...)
  165. b.Env = append(b.Env, build.Env...)
  166. b.SourceFilesCmd = append(b.SourceFilesCmd, build.SourceFilesCmd...)
  167. b.Program += build.Program
  168. b.Library += build.Library
  169. if build.Install != "" {
  170. if b.Install != "" {
  171. log.Fatalf("In file %s (target %s) included by %s (target %s): redefined Install.", f, n, build.path, build.name)
  172. }
  173. b.Install = build.Install
  174. }
  175. b.ObjectFiles = append(b.ObjectFiles, build.ObjectFiles...)
  176. // For each source file, assume we create an object file with the last char replaced
  177. // with 'o'. We can get smarter later.
  178. b.SrcDeps = append(b.SrcDeps, build.SrcDeps...)
  179. b.SrcDeps = append(b.SrcDeps, b.Program, b.Library)
  180. for _, v := range build.SourceFiles {
  181. if uptodate(v, b.SrcDeps) {
  182. continue
  183. }
  184. b.SourceFiles = append(b.SourceFiles, v)
  185. f := path.Base(v)
  186. o := f[:len(f)-1] + "o"
  187. b.ObjectFiles = append(b.ObjectFiles, o)
  188. }
  189. for _, v := range build.Include {
  190. if !path.IsAbs(v) {
  191. wd := path.Dir(f)
  192. v = path.Join(wd, v)
  193. }
  194. include(v, b)
  195. }
  196. }
  197. }
  198. func appendIfMissing(s []string, v string) []string {
  199. for _, a := range s {
  200. if a == v {
  201. return s
  202. }
  203. }
  204. return append(s, v)
  205. }
  206. func process(f string, r []*regexp.Regexp) []build {
  207. log.Printf("Processing %v", f)
  208. var builds buildfile
  209. var results []build
  210. d, err := ioutil.ReadFile(f)
  211. failOn(err)
  212. failOn(json.Unmarshal(d, &builds))
  213. for n, build := range builds {
  214. build.name = n
  215. build.jsons = make(map[string]bool)
  216. skip := true
  217. for _, re := range r {
  218. if re.MatchString(build.name) {
  219. skip = false
  220. break
  221. }
  222. }
  223. if skip {
  224. continue
  225. }
  226. log.Printf("Run %v", build.name)
  227. build.jsons[f] = true
  228. build.path = path.Dir(f)
  229. // For each source file, assume we create an object file with the last char replaced
  230. // with 'o'. We can get smarter later.
  231. s := build.SourceFiles
  232. build.SourceFiles = nil
  233. t := target(&build)
  234. deps := targetDepends(&build)
  235. for _, v := range s {
  236. if uptodate(t, deps) {
  237. continue
  238. }
  239. build.SourceFiles = append(build.SourceFiles, v)
  240. f := path.Base(v)
  241. o := f[:len(f)-1] + "o"
  242. build.ObjectFiles = appendIfMissing(build.ObjectFiles, o)
  243. }
  244. for _, v := range build.Include {
  245. include(v, &build)
  246. }
  247. results = append(results, build)
  248. }
  249. return results
  250. }
  251. func buildkernel(b *build) {
  252. if b.Kernel == nil {
  253. return
  254. }
  255. codebuf := confcode(b.path, b.Kernel)
  256. failOn(ioutil.WriteFile(b.name+".c", codebuf, 0666))
  257. }
  258. func wrapInQuote(args []string) []string {
  259. var res []string
  260. for _, a := range args {
  261. if strings.Contains(a, "=") {
  262. res = append(res, "'"+a+"'")
  263. } else {
  264. res = append(res, a)
  265. }
  266. }
  267. return res
  268. }
  269. // Is everything in deps[] older than f?
  270. func older(f string, deps []string) bool {
  271. debug("check older for %s\n", f)
  272. fi, err := os.Stat(f)
  273. // If it does not exist, that's really not our problem.
  274. // We only worry about things that exist.
  275. if err != nil {
  276. debug("\tdoesn't exist\n")
  277. return true
  278. }
  279. m := fi.ModTime()
  280. debug("older: time is %v\n", m)
  281. for _, d := range deps {
  282. debug("\tCheck %s:", d)
  283. di, err := os.Stat(d)
  284. if err != nil {
  285. continue
  286. }
  287. if !di.ModTime().After(m) {
  288. debug("%v is newer\n", di.ModTime())
  289. return true
  290. }
  291. debug("%v is older\n", di.ModTime())
  292. }
  293. debug("all is older\n")
  294. return false
  295. }
  296. func uptodate(n string, d []string) bool {
  297. debug("uptodate: %s, %v\n", n, d)
  298. if !*depends {
  299. debug("\t no\n")
  300. return false
  301. }
  302. return older(n, d)
  303. }
  304. func targetDepends(b *build) []string {
  305. return append(b.SrcDeps, fromRoot("/sys/include/u.h"), fromRoot("/sys/include/libc.h"))
  306. }
  307. func target(b *build) string {
  308. if b.Program != "" {
  309. return path.Join(b.Install, b.Program)
  310. }
  311. if b.Library != "" {
  312. return path.Join(b.Install, b.Library)
  313. }
  314. return ""
  315. }
  316. func compile(b *build) {
  317. log.Printf("Building %s\n", b.name)
  318. // N.B. Plan 9 has a very well defined include structure, just three things:
  319. // /amd64/include, /sys/include, .
  320. args := []string{
  321. "-std=c11", "-c",
  322. "-I", fromRoot("/$ARCH/include"),
  323. "-I", fromRoot("/sys/include"),
  324. "-I", ".",
  325. }
  326. args = append(args, b.Cflags...)
  327. if len(b.SourceFilesCmd) > 0 {
  328. for _, i := range b.SourceFilesCmd {
  329. cmd := exec.Command(tools["cc"], append(args, i)...)
  330. run(b, *shellhack, cmd)
  331. }
  332. return
  333. }
  334. args = append(args, b.SourceFiles...)
  335. cmd := exec.Command(tools["cc"], args...)
  336. run(b, *shellhack, cmd)
  337. }
  338. func link(b *build) {
  339. log.Printf("Linking %s\n", b.name)
  340. if len(b.SourceFilesCmd) > 0 {
  341. for _, n := range b.SourceFilesCmd {
  342. // Split off the last element of the file
  343. var ext = filepath.Ext(n)
  344. if len(ext) == 0 {
  345. log.Fatalf("refusing to overwrite extension-less source file %v", n)
  346. continue
  347. }
  348. n = n[:len(n)-len(ext)]
  349. f := path.Base(n)
  350. o := f[:len(f)] + ".o"
  351. args := []string{"-o", n, o}
  352. args = append(args, "-L", fromRoot("/$ARCH/lib"))
  353. args = append(args, b.Libs...)
  354. args = append(args, b.Oflags...)
  355. run(b, *shellhack, exec.Command(tools["ld"], args...))
  356. }
  357. return
  358. }
  359. args := []string{"-o", b.Program}
  360. args = append(args, b.ObjectFiles...)
  361. args = append(args, "-L", fromRoot("/$ARCH/lib"))
  362. args = append(args, b.Libs...)
  363. args = append(args, b.Oflags...)
  364. run(b, *shellhack, exec.Command(tools["ld"], args...))
  365. }
  366. func install(b *build) {
  367. if b.Install == "" {
  368. return
  369. }
  370. log.Printf("Installing %s\n", b.name)
  371. failOn(os.MkdirAll(b.Install, 0755))
  372. switch {
  373. case len(b.SourceFilesCmd) > 0:
  374. for _, n := range b.SourceFilesCmd {
  375. ext := filepath.Ext(n)
  376. exe := n[:len(n)-len(ext)]
  377. move(exe, b.Install)
  378. }
  379. case len(b.Program) > 0:
  380. move(b.Program, b.Install)
  381. case len(b.Library) > 0:
  382. libpath := path.Join(b.Install, b.Library)
  383. args := append([]string{"-rs", libpath}, b.ObjectFiles...)
  384. run(b, *shellhack, exec.Command(tools["ar"], args...))
  385. run(b, *shellhack, exec.Command(tools["ranlib"], libpath))
  386. }
  387. }
  388. func move(from, to string) {
  389. final := path.Join(to, from)
  390. log.Printf("move %s %s\n", from, final)
  391. _ = os.Remove(final)
  392. failOn(os.Link(from, final))
  393. failOn(os.Remove(from))
  394. }
  395. func run(b *build, pipe bool, cmd *exec.Cmd) {
  396. sh := os.Getenv("SHELL")
  397. if sh == "" {
  398. sh = "sh"
  399. }
  400. if b != nil {
  401. cmd.Env = append(os.Environ(), b.Env...)
  402. }
  403. cmd.Stdout = os.Stdout
  404. cmd.Stderr = os.Stderr
  405. if pipe {
  406. // Sh sends cmd to a shell. It's needed to enable $LD_PRELOAD tricks, see https://github.com/Harvey-OS/harvey/issues/8#issuecomment-131235178
  407. shell := exec.Command(sh)
  408. shell.Env = cmd.Env
  409. shell.Stderr = os.Stderr
  410. shell.Stdout = os.Stdout
  411. commandString := strings.Join(cmd.Args, " ")
  412. shStdin, err := shell.StdinPipe()
  413. if err != nil {
  414. log.Fatalf("cannot pipe [%v] to %s: %v", commandString, sh, err)
  415. }
  416. go func() {
  417. defer shStdin.Close()
  418. io.WriteString(shStdin, commandString)
  419. }()
  420. log.Printf("%q | %s\n", commandString, sh)
  421. failOn(shell.Run())
  422. return
  423. }
  424. log.Println(strings.Join(cmd.Args, " "))
  425. failOn(cmd.Run())
  426. }
  427. func projects(b *build, r []*regexp.Regexp) {
  428. for _, v := range b.Projects {
  429. log.Printf("Doing %s\n", strings.TrimSuffix(v, ".json"))
  430. project(v, r)
  431. }
  432. }
  433. func dirPop(s string) {
  434. fmt.Printf("Leaving directory `%v'\n", s)
  435. failOn(os.Chdir(s))
  436. }
  437. func dirPush(s string) {
  438. fmt.Printf("Entering directory `%v'\n", s)
  439. failOn(os.Chdir(s))
  440. }
  441. func runCmds(b *build, s []string) {
  442. for _, c := range s {
  443. args := adjust(strings.Split(c, " "))
  444. var exp []string
  445. for _, v := range args {
  446. e, err := filepath.Glob(v)
  447. debug("glob %v to %v err %v", v, e, err)
  448. if len(e) == 0 || err != nil {
  449. exp = append(exp, v)
  450. } else {
  451. exp = append(exp, e...)
  452. }
  453. }
  454. run(b, *shellhack, exec.Command(exp[0], exp[1:]...))
  455. }
  456. }
  457. // assumes we are in the wd of the project.
  458. func project(bf string, which []*regexp.Regexp) {
  459. cwd, err := os.Getwd()
  460. failOn(err)
  461. debug("Start new project cwd is %v", cwd)
  462. defer dirPop(cwd)
  463. dir := path.Dir(bf)
  464. root := path.Base(bf)
  465. debug("CD to %v and build using %v", dir, root)
  466. dirPush(dir)
  467. builds := process(root, which)
  468. debug("Processing %v: %d target", root, len(builds))
  469. for _, b := range builds {
  470. debug("Processing %v: %v", b.name, b)
  471. projects(&b, regexpAll)
  472. runCmds(&b, b.Pre)
  473. buildkernel(&b)
  474. if len(b.SourceFiles) > 0 || len(b.SourceFilesCmd) > 0 {
  475. compile(&b)
  476. }
  477. if b.Program != "" || len(b.SourceFilesCmd) > 0 {
  478. link(&b)
  479. }
  480. install(&b)
  481. runCmds(&b, b.Post)
  482. }
  483. install(b)
  484. run(b, b.Post)
  485. }
  486. func main() {
  487. // A small amount of setup is done in the paths*.go files. They are
  488. // OS-specific path setup/manipulation. "harvey" is set there and $PATH is
  489. // adjusted.
  490. var err error
  491. flag.Parse()
  492. findTools(os.Getenv("TOOLPREFIX"))
  493. cwd, err = os.Getwd()
  494. failOn(err)
  495. if os.Getenv("CC") == "" {
  496. log.Fatalf("You need to set the CC environment variable (e.g. gcc, clang, clang-3.6, ...)")
  497. }
  498. a := os.Getenv("ARCH")
  499. if a == "" || !arch[a] {
  500. s := []string{}
  501. for i := range arch {
  502. s = append(s, i)
  503. }
  504. log.Fatalf("You need to set the ARCH environment variable from: %v", s)
  505. }
  506. // ensure this is exported, in case we used a default value
  507. os.Setenv("HARVEY", harvey)
  508. if os.Getenv("LD_PRELOAD") != "" {
  509. log.Println("Using shellhack")
  510. *shellhack = true
  511. }
  512. // If no args, assume 'build.json'
  513. // Otherwise the first argument is either
  514. // - the path to a json file
  515. // - a directory containing a 'build.json' file
  516. // - a regular expression to apply assuming 'build.json'
  517. // Further arguments are regular expressions.
  518. consumedArgs := 0
  519. bf := ""
  520. if len(flag.Args()) == 0 {
  521. f, err := findBuildfile("build.json")
  522. failOn(err)
  523. bf = f
  524. } else {
  525. f, err := findBuildfile(flag.Arg(0))
  526. failOn(err)
  527. if f == "" {
  528. f, err := findBuildfile("build.json")
  529. failOn(err)
  530. bf = f
  531. } else {
  532. consumedArgs = 1
  533. bf = f
  534. }
  535. }
  536. bf, err := findBuildfile(f)
  537. failOn(err)
  538. re := []*regexp.Regexp{regexp.MustCompile(".")}
  539. if len(flag.Args()) > 1 {
  540. re = re[:0]
  541. for _, r := range flag.Args()[1:] {
  542. rx, err := regexp.Compile(r)
  543. failOn(err)
  544. re = append(re, rx)
  545. }
  546. }
  547. project(bf, re)
  548. }
  549. func findTools(toolprefix string) {
  550. var err error
  551. for k, v := range tools {
  552. if x := os.Getenv(strings.ToUpper(k)); x != "" {
  553. v = x
  554. }
  555. v, err = exec.LookPath(toolprefix + v)
  556. failOn(err)
  557. tools[k] = v
  558. }
  559. }
  560. // disambiguate the buildfile argument
  561. func findBuildfile(f string) (string, error) {
  562. if strings.HasSuffix(f, ".json") {
  563. if fi, err := os.Stat(f); err == nil && !fi.IsDir() {
  564. return f, nil
  565. }
  566. }
  567. return "", fmt.Errorf("unable to find buildfile (tried %s)", strings.Join(try, ", "))
  568. }