build.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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: 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: CC, 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. ToolOpts map[string][]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.Cflags = adjust(b.Cflags)
  85. b.Oflags = adjust(b.Oflags)
  86. b.SourceFiles = adjust(b.SourceFiles)
  87. b.SourceFilesCmd = adjust(b.SourceFilesCmd)
  88. b.ObjectFiles = adjust(b.ObjectFiles)
  89. b.Include = adjust(b.Include)
  90. b.Install = fromRoot(b.Install)
  91. for i, e := range b.Env {
  92. b.Env[i] = os.ExpandEnv(e)
  93. }
  94. r[k] = b
  95. }
  96. *bf = r
  97. return nil
  98. }
  99. var (
  100. cwd string
  101. harvey string
  102. regexpAll = []*regexp.Regexp{regexp.MustCompile(".")}
  103. // findTools looks at all env vars and absolutizes these paths
  104. // also respects TOOLPREFIX
  105. tools = map[string]string{
  106. "cc": "gcc",
  107. "ar": "ar",
  108. "ld": "ld",
  109. "ranlib": "ranlib",
  110. "strip": "strip",
  111. "sh": "sh",
  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. shellhack = flag.Bool("shellhack", false, "spawn every command in a shell (forced on if LD_PRELOAD is set)")
  120. )
  121. func debug(fmt string, s ...interface{}) {
  122. if *debugPrint {
  123. log.Printf(fmt, s...)
  124. }
  125. }
  126. // fail with message, if err is not nil
  127. func failOn(err error) {
  128. if err != nil {
  129. log.Fatalf("%v", err)
  130. }
  131. }
  132. func adjust(s []string) []string {
  133. for i, v := range s {
  134. s[i] = fromRoot(v)
  135. }
  136. return s
  137. }
  138. // return the given absolute path as an absolute path rooted at the harvey tree.
  139. func fromRoot(p string) string {
  140. p = os.ExpandEnv(p)
  141. if path.IsAbs(p) {
  142. return path.Join(harvey, p)
  143. }
  144. return p
  145. }
  146. // Sh sends cmd to a shell. It's needed to enable $LD_PRELOAD tricks,
  147. // see https://github.com/Harvey-OS/harvey/issues/8#issuecomment-131235178
  148. func sh(cmd *exec.Cmd) {
  149. shell := exec.Command(tools["sh"])
  150. shell.Env = cmd.Env
  151. if cmd.Args[0] == tools["sh"] && cmd.Args[1] == "-c" {
  152. cmd.Args = cmd.Args[2:]
  153. }
  154. commandString := strings.Join(cmd.Args, " ")
  155. if shStdin, e := shell.StdinPipe(); e == nil {
  156. go func() {
  157. defer shStdin.Close()
  158. io.WriteString(shStdin, commandString)
  159. }()
  160. } else {
  161. log.Fatalf("cannot pipe [%v] to %s: %v", commandString, tools["sh"], e)
  162. }
  163. shell.Stderr = os.Stderr
  164. shell.Stdout = os.Stdout
  165. debug("%q | sh\n", commandString)
  166. failOn(shell.Run())
  167. }
  168. func process(f, which string, b *build) {
  169. r := regexp.MustCompile(which)
  170. if b.jsons[f] {
  171. return
  172. }
  173. b.jsons[f] = true
  174. log.Printf("Including %v", f)
  175. d, err := ioutil.ReadFile(f)
  176. failOn(err)
  177. var builds buildfile
  178. failOn(json.Unmarshal(d, &builds))
  179. for n, build := range builds {
  180. log.Printf("Merging %v", n)
  181. for t,v := range build.ToolOpts {
  182. b.ToolOpts[t] = v
  183. }
  184. b.SourceFiles = append(b.SourceFiles, build.SourceFiles...)
  185. b.Cflags = append(b.Cflags, build.Cflags...)
  186. b.Oflags = append(b.Oflags, build.Oflags...)
  187. b.Pre = append(b.Pre, build.Pre...)
  188. b.Post = append(b.Post, build.Post...)
  189. b.Libs = append(b.Libs, build.Libs...)
  190. b.Projects = append(b.Projects, build.Projects...)
  191. b.Env = append(b.Env, build.Env...)
  192. b.SourceFilesCmd = append(b.SourceFilesCmd, build.SourceFilesCmd...)
  193. b.Program += build.Program
  194. b.Library += build.Library
  195. if build.Install != "" {
  196. if b.Install != "" {
  197. log.Fatalf("In file %s (target %s) included by %s (target %s): redefined Install.", f, n, build.path, build.name)
  198. }
  199. b.Install = build.Install
  200. }
  201. b.ObjectFiles = append(b.ObjectFiles, build.ObjectFiles...)
  202. // For each source file, assume we create an object file with the last char replaced
  203. // with 'o'. We can get smarter later.
  204. for _, v := range build.SourceFiles {
  205. f := path.Base(v)
  206. o := f[:len(f)-1] + "o"
  207. b.ObjectFiles = append(b.ObjectFiles, o)
  208. }
  209. for _, v := range build.Include {
  210. if !path.IsAbs(v) {
  211. wd := path.Dir(f)
  212. v = path.Join(wd, v)
  213. }
  214. include(v, b)
  215. }
  216. }
  217. }
  218. func appendIfMissing(s []string, v string) []string {
  219. for _, a := range s {
  220. if a == v {
  221. return s
  222. }
  223. }
  224. return append(s, v)
  225. }
  226. func process(f string, r []*regexp.Regexp) []build {
  227. log.Printf("Processing %v", f)
  228. var builds buildfile
  229. var results []build
  230. d, err := ioutil.ReadFile(f)
  231. failOn(err)
  232. failOn(json.Unmarshal(d, &builds))
  233. for n, build := range builds {
  234. build.name = n
  235. build.jsons = make(map[string]bool)
  236. build.ToolOpts = make(map[string][]string)
  237. skip := true
  238. for _, re := range r {
  239. if re.MatchString(build.name) {
  240. skip = false
  241. break
  242. }
  243. }
  244. if skip {
  245. continue
  246. }
  247. log.Printf("Run %v", build.name)
  248. build.jsons[f] = true
  249. build.path = path.Dir(f)
  250. // For each source file, assume we create an object file with the last char replaced
  251. // with 'o'. We can get smarter later.
  252. for _, v := range build.SourceFiles {
  253. f := path.Base(v)
  254. o := f[:len(f)-1] + "o"
  255. build.ObjectFiles = appendIfMissing(build.ObjectFiles, o)
  256. }
  257. for _, v := range build.Include {
  258. include(v, &build)
  259. }
  260. results = append(results, build)
  261. }
  262. return results
  263. }
  264. func buildkernel(b *build) {
  265. if b.Kernel == nil {
  266. return
  267. }
  268. codebuf := confcode(b.path, b.Kernel)
  269. failOn(ioutil.WriteFile(b.name+".c", codebuf, 0666))
  270. }
  271. func compile(b *build) {
  272. log.Printf("Building %s\n", b.name)
  273. // N.B. Plan 9 has a very well defined include structure, just three things:
  274. // /amd64/include, /sys/include, .
  275. args := []string{
  276. "-std=c11", "-c",
  277. "-I", fromRoot("/$ARCH/include"),
  278. "-I", fromRoot("/sys/include"),
  279. "-I", ".",
  280. }
  281. if toolOpts, ok := b.ToolOpts[tools["cc"]]; ok {
  282. args = append(args, toolOpts...)
  283. }
  284. args = append(args, b.Cflags...)
  285. if len(b.SourceFilesCmd) > 0 {
  286. for _, i := range b.SourceFilesCmd {
  287. cmd := exec.Command(tools["cc"], append(args, i)...)
  288. run(b, *shellhack, cmd)
  289. }
  290. return
  291. }
  292. args = append(args, b.SourceFiles...)
  293. cmd := exec.Command(tools["cc"], args...)
  294. run(b, *shellhack, cmd)
  295. }
  296. func link(b *build) {
  297. log.Printf("Linking %s\n", b.name)
  298. if len(b.SourceFilesCmd) > 0 {
  299. for _, n := range b.SourceFilesCmd {
  300. // Split off the last element of the file
  301. var ext = filepath.Ext(n)
  302. if len(ext) == 0 {
  303. log.Fatalf("refusing to overwrite extension-less source file %v", n)
  304. continue
  305. }
  306. n = n[:len(n)-len(ext)]
  307. f := path.Base(n)
  308. o := f[:len(f)] + ".o"
  309. args := []string{"-o", n, o}
  310. args = append(args, "-L", fromRoot("/$ARCH/lib"))
  311. args = append(args, b.Libs...)
  312. args = append(args, b.Oflags...)
  313. if toolOpts, ok := b.ToolOpts[tools["ld"]]; ok {
  314. args = append(args, toolOpts...)
  315. }
  316. run(b, *shellhack, exec.Command(tools["ld"], args...))
  317. }
  318. return
  319. }
  320. args := []string{"-o", b.Program}
  321. if toolOpts, ok := b.ToolOpts[tools["ld"]]; ok {
  322. args = append(args, toolOpts...)
  323. }
  324. args = append(args, b.ObjectFiles...)
  325. args = append(args, "-L", fromRoot("/$ARCH/lib"))
  326. args = append(args, b.Libs...)
  327. args = append(args, b.Oflags...)
  328. run(b, *shellhack, exec.Command(tools["ld"], args...))
  329. }
  330. func install(b *build) {
  331. if b.Install == "" {
  332. return
  333. }
  334. log.Printf("Installing %s\n", b.name)
  335. failOn(os.MkdirAll(b.Install, 0755))
  336. switch {
  337. case len(b.SourceFilesCmd) > 0:
  338. for _, n := range b.SourceFilesCmd {
  339. ext := filepath.Ext(n)
  340. exe := n[:len(n)-len(ext)]
  341. move(exe, b.Install)
  342. }
  343. case len(b.Program) > 0:
  344. move(b.Program, b.Install)
  345. case len(b.Library) > 0:
  346. libpath := path.Join(b.Install, b.Library)
  347. args := append([]string{"-rs", libpath}, b.ObjectFiles...)
  348. run(b, *shellhack, exec.Command(tools["ar"], args...))
  349. run(b, *shellhack, exec.Command(tools["ranlib"], libpath))
  350. }
  351. }
  352. func move(from, to string) {
  353. final := path.Join(to, from)
  354. log.Printf("move %s %s\n", from, final)
  355. _ = os.Remove(final)
  356. failOn(os.Link(from, final))
  357. failOn(os.Remove(from))
  358. }
  359. func run(b *build, pipe bool, cmd *exec.Cmd) {
  360. if b != nil {
  361. cmd.Env = append(os.Environ(), b.Env...)
  362. }
  363. cmd.Stdout = os.Stdout
  364. cmd.Stderr = os.Stderr
  365. if pipe {
  366. // 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
  367. shell := exec.Command(tools["sh"])
  368. shell.Env = cmd.Env
  369. shell.Stderr = os.Stderr
  370. shell.Stdout = os.Stdout
  371. commandString := strings.Join(cmd.Args, " ")
  372. shStdin, err := shell.StdinPipe()
  373. if err != nil {
  374. log.Fatalf("cannot pipe [%v] to %s: %v", commandString, tools["sh"], err)
  375. }
  376. go func() {
  377. defer shStdin.Close()
  378. io.WriteString(shStdin, commandString)
  379. }()
  380. log.Printf("%q | sh\n", commandString)
  381. failOn(shell.Run())
  382. return
  383. }
  384. log.Println(strings.Join(cmd.Args, " "))
  385. failOn(cmd.Run())
  386. }
  387. func projects(b *build, r []*regexp.Regexp) {
  388. for _, v := range b.Projects {
  389. log.Printf("Doing %s\n", strings.TrimSuffix(v, ".json"))
  390. project(v, r)
  391. }
  392. }
  393. func dirPop(s string) {
  394. fmt.Printf("Leaving directory `%v'\n", s)
  395. failOn(os.Chdir(s))
  396. }
  397. func dirPush(s string) {
  398. fmt.Printf("Entering directory `%v'\n", s)
  399. failOn(os.Chdir(s))
  400. }
  401. // assumes we are in the wd of the project.
  402. func project(bf string, which []*regexp.Regexp) {
  403. cwd, err := os.Getwd()
  404. failOn(err)
  405. debug("Start new project cwd is %v", cwd)
  406. defer dirPop(cwd)
  407. dir := path.Dir(bf)
  408. root := path.Base(bf)
  409. debug("CD to %v and build using %v", dir, root)
  410. dirPush(dir)
  411. builds := process(root, which)
  412. debug("Processing %v: %d target", root, len(builds))
  413. for _, b := range builds {
  414. debug("Processing %v: %v", b.name, b)
  415. projects(&b, regexpAll)
  416. for _, c := range b.Pre {
  417. // this is a hack: we just pass the command through as an exec.Cmd
  418. run(&b, true, exec.Command(c))
  419. }
  420. buildkernel(&b)
  421. if len(b.SourceFiles) > 0 || len(b.SourceFilesCmd) > 0 {
  422. compile(&b)
  423. }
  424. if b.Program != "" || len(b.SourceFilesCmd) > 0 {
  425. link(&b)
  426. }
  427. install(&b)
  428. for _, c := range b.Post {
  429. run(&b, true, exec.Command(c))
  430. }
  431. }
  432. install(b)
  433. run(b, b.Post)
  434. }
  435. func main() {
  436. // A small amount of setup is done in the paths*.go files. They are
  437. // OS-specific path setup/manipulation. "harvey" is set there and $PATH is
  438. // adjusted.
  439. var err error
  440. findTools(os.Getenv("TOOLPREFIX"))
  441. flag.Parse()
  442. cwd, err = os.Getwd()
  443. failOn(err)
  444. a := os.Getenv("ARCH")
  445. if a == "" || !arch[a] {
  446. s := []string{}
  447. for i := range arch {
  448. s = append(s, i)
  449. }
  450. log.Fatalf("You need to set the ARCH environment variable from: %v", s)
  451. }
  452. // ensure this is exported, in case we used a default value
  453. os.Setenv("HARVEY", harvey)
  454. if os.Getenv("LD_PRELOAD") != "" {
  455. log.Println("Using shellhack")
  456. *shellhack = true
  457. }
  458. // If no args, assume 'build.json'
  459. // If 1 arg, that's a dir or file name.
  460. // if two args, that's a dir and a regular expression.
  461. f := "build.json"
  462. if len(flag.Args()) > 0 {
  463. f = flag.Arg(0)
  464. }
  465. bf, err := findBuildfile(f)
  466. failOn(err)
  467. re := []*regexp.Regexp{regexp.MustCompile(".")}
  468. if len(flag.Args()) > 1 {
  469. re = re[:0]
  470. for _, r := range flag.Args()[1:] {
  471. rx, err := regexp.Compile(r)
  472. failOn(err)
  473. re = append(re, rx)
  474. }
  475. }
  476. project(bf, re)
  477. }
  478. func findTools(toolprefix string) {
  479. var err error
  480. for k, v := range tools {
  481. if x := os.Getenv(strings.ToUpper(k)); x != "" {
  482. v = x
  483. }
  484. v, err = exec.LookPath(toolprefix + v)
  485. failOn(err)
  486. tools[k] = v
  487. }
  488. }
  489. // disambiguate the buildfile argument
  490. func findBuildfile(f string) (string, error) {
  491. try := []string{
  492. f,
  493. path.Join(f, "build.json"),
  494. fromRoot(path.Join("/sys/src", f+".json")),
  495. fromRoot(path.Join("/sys/src", f, "build.json")),
  496. }
  497. for _, p := range try {
  498. if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
  499. return p, nil
  500. }
  501. }
  502. return "", fmt.Errorf("unable to find buildfile (tried %s)", strings.Join(try, ", "))
  503. }