build.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // +build ignore
  7. package main
  8. import (
  9. "archive/tar"
  10. "archive/zip"
  11. "bytes"
  12. "compress/gzip"
  13. "flag"
  14. "fmt"
  15. "io"
  16. "io/ioutil"
  17. "log"
  18. "os"
  19. "os/exec"
  20. "os/user"
  21. "path/filepath"
  22. "regexp"
  23. "runtime"
  24. "strconv"
  25. "strings"
  26. "text/template"
  27. "time"
  28. )
  29. var (
  30. versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
  31. goarch string
  32. goos string
  33. noupgrade bool
  34. version string
  35. goVersion float64
  36. race bool
  37. )
  38. type target struct {
  39. name string
  40. buildPkg string
  41. binaryName string
  42. archiveFiles []archiveFile
  43. debianFiles []archiveFile
  44. }
  45. type archiveFile struct {
  46. src string
  47. dst string
  48. perm os.FileMode
  49. }
  50. var targets = map[string]target{
  51. "all": {
  52. // Only valid for the "build" and "install" commands as it lacks all
  53. // the archive creation stuff.
  54. buildPkg: "./cmd/...",
  55. },
  56. "syncthing": {
  57. // The default target for "build", "install", "tar", "zip", "deb", etc.
  58. name: "syncthing",
  59. buildPkg: "./cmd/syncthing",
  60. binaryName: "syncthing", // .exe will be added automatically for Windows builds
  61. archiveFiles: []archiveFile{
  62. {src: "{{binary}}", dst: "{{binary}}", perm: 0755},
  63. {src: "README.md", dst: "README.txt", perm: 0644},
  64. {src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
  65. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
  66. // All files from etc/ and extra/ added automatically in init().
  67. },
  68. debianFiles: []archiveFile{
  69. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
  70. {src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
  71. {src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
  72. {src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
  73. {src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
  74. {src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
  75. {src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
  76. {src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
  77. {src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
  78. {src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
  79. {src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
  80. {src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
  81. {src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
  82. {src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
  83. {src: "etc/linux-systemd/system/[email protected]", dst: "deb/lib/systemd/system/[email protected]", perm: 0644},
  84. {src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
  85. {src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
  86. },
  87. },
  88. }
  89. func init() {
  90. // The "syncthing" target includes a few more files found in the "etc"
  91. // and "extra" dirs.
  92. syncthingPkg := targets["syncthing"]
  93. for _, file := range listFiles("etc") {
  94. syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
  95. }
  96. for _, file := range listFiles("extra") {
  97. syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
  98. }
  99. for _, file := range listFiles("extra") {
  100. syncthingPkg.debianFiles = append(syncthingPkg.debianFiles, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
  101. }
  102. targets["syncthing"] = syncthingPkg
  103. }
  104. const minGoVersion = 1.3
  105. func main() {
  106. log.SetOutput(os.Stdout)
  107. log.SetFlags(0)
  108. // If GOPATH isn't set, set it correctly with the assumption that we are
  109. // in $GOPATH/src/github.com/syncthing/syncthing.
  110. if os.Getenv("GOPATH") == "" {
  111. cwd, err := os.Getwd()
  112. if err != nil {
  113. log.Fatal(err)
  114. }
  115. gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
  116. log.Println("GOPATH is", gopath)
  117. os.Setenv("GOPATH", gopath)
  118. }
  119. // We use Go 1.5+ vendoring.
  120. os.Setenv("GO15VENDOREXPERIMENT", "1")
  121. // Set path to $GOPATH/bin:$PATH so that we can for sure find tools we
  122. // might have installed during "build.go setup".
  123. os.Setenv("PATH", fmt.Sprintf("%s%cbin%c%s", os.Getenv("GOPATH"), os.PathSeparator, os.PathListSeparator, os.Getenv("PATH")))
  124. flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
  125. flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
  126. flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
  127. flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
  128. flag.BoolVar(&race, "race", race, "Use race detector")
  129. flag.Parse()
  130. switch goarch {
  131. case "386", "amd64", "arm", "arm64", "ppc64", "ppc64le":
  132. break
  133. default:
  134. log.Printf("Unknown goarch %q; proceed with caution!", goarch)
  135. }
  136. goVersion, _ = checkRequiredGoVersion()
  137. // Invoking build.go with no parameters at all is equivalent to "go run
  138. // build.go install all" as that builds everything (incrementally),
  139. // which is what you want for maximum error checking during development.
  140. if flag.NArg() == 0 {
  141. var tags []string
  142. if noupgrade {
  143. tags = []string{"noupgrade"}
  144. }
  145. install(targets["all"], tags)
  146. vet("./cmd/syncthing")
  147. vet("./lib/...")
  148. lint("./cmd/syncthing")
  149. lint("./lib/...")
  150. return
  151. }
  152. // Otherwise, with any command given but not a target, the target is
  153. // "syncthing". So "go run build.go install" is "go run build.go install
  154. // syncthing" etc.
  155. targetName := "syncthing"
  156. if flag.NArg() > 1 {
  157. targetName = flag.Arg(1)
  158. }
  159. target, ok := targets[targetName]
  160. if !ok {
  161. log.Fatalln("Unknown target", target)
  162. }
  163. cmd := flag.Arg(0)
  164. switch cmd {
  165. case "setup":
  166. setup()
  167. case "install":
  168. var tags []string
  169. if noupgrade {
  170. tags = []string{"noupgrade"}
  171. }
  172. install(target, tags)
  173. case "build":
  174. var tags []string
  175. if noupgrade {
  176. tags = []string{"noupgrade"}
  177. }
  178. build(target, tags)
  179. case "test":
  180. test("./lib/...", "./cmd/...")
  181. case "bench":
  182. bench("./lib/...", "./cmd/...")
  183. case "assets":
  184. rebuildAssets()
  185. case "xdr":
  186. xdr()
  187. case "translate":
  188. translate()
  189. case "transifex":
  190. transifex()
  191. case "tar":
  192. buildTar(target)
  193. case "zip":
  194. buildZip(target)
  195. case "deb":
  196. buildDeb(target)
  197. case "clean":
  198. clean()
  199. case "vet":
  200. vet("./cmd/syncthing")
  201. vet("./lib/...")
  202. case "lint":
  203. lint("./cmd/syncthing")
  204. lint("./lib/...")
  205. default:
  206. log.Fatalf("Unknown command %q", cmd)
  207. }
  208. }
  209. func checkRequiredGoVersion() (float64, bool) {
  210. re := regexp.MustCompile(`go(\d+\.\d+)`)
  211. ver := runtime.Version()
  212. if m := re.FindStringSubmatch(ver); len(m) == 2 {
  213. vs := string(m[1])
  214. // This is a standard go build. Verify that it's new enough.
  215. f, err := strconv.ParseFloat(vs, 64)
  216. if err != nil {
  217. log.Printf("*** Couldn't parse Go version out of %q.\n*** This isn't known to work, proceed on your own risk.", vs)
  218. return 0, false
  219. }
  220. if f < 1.5 {
  221. log.Printf("*** Go version %.01f doesn't support the vendoring mechanism.\n*** Ensure correct dependencies in your $GOPATH.", f)
  222. } else if f < minGoVersion {
  223. log.Fatalf("*** Go version %.01f is less than required %.01f.\n*** This is known not to work, not proceeding.", f, minGoVersion)
  224. }
  225. return f, true
  226. }
  227. log.Printf("*** Unknown Go version %q.\n*** This isn't known to work, proceed on your own risk.", ver)
  228. return 0, false
  229. }
  230. func setup() {
  231. runPrint("go", "get", "-v", "golang.org/x/tools/cmd/cover")
  232. runPrint("go", "get", "-v", "golang.org/x/net/html")
  233. runPrint("go", "get", "-v", "github.com/FiloSottile/gvt")
  234. runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
  235. runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
  236. runPrint("go", "get", "-v", "bitbucket.org/tebeka/go2xunit")
  237. }
  238. func test(pkgs ...string) {
  239. lazyRebuildAssets()
  240. useRace := runtime.GOARCH == "amd64"
  241. switch runtime.GOOS {
  242. case "darwin", "linux", "freebsd", "windows":
  243. default:
  244. useRace = false
  245. }
  246. if useRace {
  247. runPrint("go", append([]string{"test", "-short", "-race", "-timeout", "60s"}, pkgs...)...)
  248. } else {
  249. runPrint("go", append([]string{"test", "-short", "-timeout", "60s"}, pkgs...)...)
  250. }
  251. }
  252. func bench(pkgs ...string) {
  253. lazyRebuildAssets()
  254. runPrint("go", append([]string{"test", "-run", "NONE", "-bench", "."}, pkgs...)...)
  255. }
  256. func install(target target, tags []string) {
  257. lazyRebuildAssets()
  258. cwd, err := os.Getwd()
  259. if err != nil {
  260. log.Fatal(err)
  261. }
  262. os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
  263. args := []string{"install", "-v", "-ldflags", ldflags()}
  264. if len(tags) > 0 {
  265. args = append(args, "-tags", strings.Join(tags, ","))
  266. }
  267. if race {
  268. args = append(args, "-race")
  269. }
  270. args = append(args, target.buildPkg)
  271. os.Setenv("GOOS", goos)
  272. os.Setenv("GOARCH", goarch)
  273. runPrint("go", args...)
  274. }
  275. func build(target target, tags []string) {
  276. lazyRebuildAssets()
  277. rmr(target.binaryName)
  278. args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
  279. if len(tags) > 0 {
  280. args = append(args, "-tags", strings.Join(tags, ","))
  281. }
  282. if race {
  283. args = append(args, "-race")
  284. }
  285. args = append(args, target.buildPkg)
  286. os.Setenv("GOOS", goos)
  287. os.Setenv("GOARCH", goarch)
  288. runPrint("go", args...)
  289. }
  290. func buildTar(target target) {
  291. name := archiveName(target)
  292. filename := name + ".tar.gz"
  293. var tags []string
  294. if noupgrade {
  295. tags = []string{"noupgrade"}
  296. name += "-noupgrade"
  297. }
  298. build(target, tags)
  299. if goos == "darwin" {
  300. macosCodesign(target.binaryName)
  301. }
  302. for i := range target.archiveFiles {
  303. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
  304. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
  305. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  306. }
  307. tarGz(filename, target.archiveFiles)
  308. log.Println(filename)
  309. }
  310. func buildZip(target target) {
  311. target.binaryName += ".exe"
  312. name := archiveName(target)
  313. filename := name + ".zip"
  314. var tags []string
  315. if noupgrade {
  316. tags = []string{"noupgrade"}
  317. name += "-noupgrade"
  318. }
  319. build(target, tags)
  320. for i := range target.archiveFiles {
  321. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
  322. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
  323. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  324. }
  325. zipFile(filename, target.archiveFiles)
  326. log.Println(filename)
  327. }
  328. func buildDeb(target target) {
  329. os.RemoveAll("deb")
  330. // "goarch" here is set to whatever the Debian packages expect. We correct
  331. // "it to what we actually know how to build and keep the Debian variant
  332. // "name in "debarch".
  333. debarch := goarch
  334. switch goarch {
  335. case "i386":
  336. goarch = "386"
  337. case "armel", "armhf":
  338. goarch = "arm"
  339. }
  340. build(target, []string{"noupgrade"})
  341. for i := range target.debianFiles {
  342. target.debianFiles[i].src = strings.Replace(target.debianFiles[i].src, "{{binary}}", target.binaryName, 1)
  343. target.debianFiles[i].dst = strings.Replace(target.debianFiles[i].dst, "{{binary}}", target.binaryName, 1)
  344. }
  345. for _, af := range target.debianFiles {
  346. if err := copyFile(af.src, af.dst, af.perm); err != nil {
  347. log.Fatal(err)
  348. }
  349. }
  350. os.MkdirAll("deb/DEBIAN", 0755)
  351. data := map[string]string{
  352. "name": target.name,
  353. "arch": debarch,
  354. "version": version[1:],
  355. "date": time.Now().Format(time.RFC1123),
  356. }
  357. debTemplateFiles := append(listFiles("debian/common"), listFiles("debian/"+target.name)...)
  358. for _, file := range debTemplateFiles {
  359. tpl, err := template.New(filepath.Base(file)).ParseFiles(file)
  360. if err != nil {
  361. log.Fatal(err)
  362. }
  363. outFile := filepath.Join("deb/DEBIAN", filepath.Base(file))
  364. out, err := os.Create(outFile)
  365. if err != nil {
  366. log.Fatal(err)
  367. }
  368. if err := tpl.Execute(out, data); err != nil {
  369. log.Fatal(err)
  370. }
  371. if err := out.Close(); err != nil {
  372. log.Fatal(err)
  373. }
  374. info, _ := os.Lstat(file)
  375. os.Chmod(outFile, info.Mode())
  376. }
  377. }
  378. func copyFile(src, dst string, perm os.FileMode) error {
  379. dstDir := filepath.Dir(dst)
  380. os.MkdirAll(dstDir, 0755) // ignore error
  381. srcFd, err := os.Open(src)
  382. if err != nil {
  383. return err
  384. }
  385. defer srcFd.Close()
  386. dstFd, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
  387. if err != nil {
  388. return err
  389. }
  390. defer dstFd.Close()
  391. _, err = io.Copy(dstFd, srcFd)
  392. return err
  393. }
  394. func listFiles(dir string) []string {
  395. var res []string
  396. filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  397. if err != nil {
  398. return err
  399. }
  400. if fi.Mode().IsRegular() {
  401. res = append(res, path)
  402. }
  403. return nil
  404. })
  405. return res
  406. }
  407. func rebuildAssets() {
  408. runPipe("lib/auto/gui.files.go", "go", "run", "script/genassets.go", "gui")
  409. }
  410. func lazyRebuildAssets() {
  411. if shouldRebuildAssets() {
  412. rebuildAssets()
  413. }
  414. }
  415. func shouldRebuildAssets() bool {
  416. info, err := os.Stat("lib/auto/gui.files.go")
  417. if err != nil {
  418. // If the file doesn't exist, we must rebuild it
  419. return true
  420. }
  421. // Check if any of the files in gui/ are newer than the asset file. If
  422. // so we should rebuild it.
  423. currentBuild := info.ModTime()
  424. assetsAreNewer := false
  425. filepath.Walk("gui", func(path string, info os.FileInfo, err error) error {
  426. if err != nil {
  427. return err
  428. }
  429. if assetsAreNewer {
  430. return nil
  431. }
  432. assetsAreNewer = info.ModTime().After(currentBuild)
  433. return nil
  434. })
  435. return assetsAreNewer
  436. }
  437. func xdr() {
  438. runPrint("go", "generate", "./lib/discover", "./lib/db", "./lib/protocol", "./lib/relay/protocol")
  439. }
  440. func translate() {
  441. os.Chdir("gui/default/assets/lang")
  442. runPipe("lang-en-new.json", "go", "run", "../../../../script/translate.go", "lang-en.json", "../../../")
  443. os.Remove("lang-en.json")
  444. err := os.Rename("lang-en-new.json", "lang-en.json")
  445. if err != nil {
  446. log.Fatal(err)
  447. }
  448. os.Chdir("../../../..")
  449. }
  450. func transifex() {
  451. os.Chdir("gui/default/assets/lang")
  452. runPrint("go", "run", "../../../../script/transifexdl.go")
  453. }
  454. func clean() {
  455. rmr("bin")
  456. rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/syncthing", goos, goarch)))
  457. }
  458. func ldflags() string {
  459. sep := '='
  460. if goVersion > 0 && goVersion < 1.5 {
  461. sep = ' '
  462. }
  463. b := new(bytes.Buffer)
  464. b.WriteString("-w")
  465. fmt.Fprintf(b, " -X main.Version%c%s", sep, version)
  466. fmt.Fprintf(b, " -X main.BuildStamp%c%d", sep, buildStamp())
  467. fmt.Fprintf(b, " -X main.BuildUser%c%s", sep, buildUser())
  468. fmt.Fprintf(b, " -X main.BuildHost%c%s", sep, buildHost())
  469. return b.String()
  470. }
  471. func rmr(paths ...string) {
  472. for _, path := range paths {
  473. log.Println("rm -r", path)
  474. os.RemoveAll(path)
  475. }
  476. }
  477. func getReleaseVersion() (string, error) {
  478. fd, err := os.Open("RELEASE")
  479. if err != nil {
  480. return "", err
  481. }
  482. defer fd.Close()
  483. bs, err := ioutil.ReadAll(fd)
  484. if err != nil {
  485. return "", err
  486. }
  487. return string(bytes.TrimSpace(bs)), nil
  488. }
  489. func getGitVersion() (string, error) {
  490. v, err := runError("git", "describe", "--always", "--dirty")
  491. if err != nil {
  492. return "", err
  493. }
  494. v = versionRe.ReplaceAllFunc(v, func(s []byte) []byte {
  495. s[0] = '+'
  496. return s
  497. })
  498. return string(v), nil
  499. }
  500. func getVersion() string {
  501. // First try for a RELEASE file,
  502. if ver, err := getReleaseVersion(); err == nil {
  503. return ver
  504. }
  505. // ... then see if we have a Git tag.
  506. if ver, err := getGitVersion(); err == nil {
  507. if strings.Contains(ver, "-") {
  508. // The version already contains a hash and stuff. See if we can
  509. // find a current branch name to tack onto it as well.
  510. return ver + getBranchSuffix()
  511. }
  512. return ver
  513. }
  514. // This seems to be a dev build.
  515. return "unknown-dev"
  516. }
  517. func getBranchSuffix() string {
  518. bs, err := runError("git", "branch", "-a", "--contains")
  519. if err != nil {
  520. return ""
  521. }
  522. branches := strings.Split(string(bs), "\n")
  523. if len(branches) == 0 {
  524. return ""
  525. }
  526. branch := ""
  527. for i, candidate := range branches {
  528. if strings.HasPrefix(candidate, "*") {
  529. // This is the current branch. Select it!
  530. branch = strings.TrimLeft(candidate, " \t*")
  531. break
  532. } else if i == 0 {
  533. // Otherwise the first branch in the list will do.
  534. branch = strings.TrimSpace(branch)
  535. }
  536. }
  537. if branch == "" {
  538. return ""
  539. }
  540. // The branch name may be on the form "remotes/origin/foo" from which we
  541. // just want "foo".
  542. parts := strings.Split(branch, "/")
  543. if len(parts) == 0 || len(parts[len(parts)-1]) == 0 {
  544. return ""
  545. }
  546. branch = parts[len(parts)-1]
  547. if branch == "master" {
  548. // master builds are the default.
  549. return ""
  550. }
  551. validBranchRe := regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
  552. if !validBranchRe.MatchString(branch) {
  553. // There's some odd stuff in the branch name. Better skip it.
  554. return ""
  555. }
  556. return "-" + branch
  557. }
  558. func buildStamp() int64 {
  559. bs, err := runError("git", "show", "-s", "--format=%ct")
  560. if err != nil {
  561. return time.Now().Unix()
  562. }
  563. s, _ := strconv.ParseInt(string(bs), 10, 64)
  564. return s
  565. }
  566. func buildUser() string {
  567. u, err := user.Current()
  568. if err != nil {
  569. return "unknown-user"
  570. }
  571. return strings.Replace(u.Username, " ", "-", -1)
  572. }
  573. func buildHost() string {
  574. h, err := os.Hostname()
  575. if err != nil {
  576. return "unknown-host"
  577. }
  578. return h
  579. }
  580. func buildEnvironment() string {
  581. if v := os.Getenv("ENVIRONMENT"); len(v) > 0 {
  582. return v
  583. }
  584. return "default"
  585. }
  586. func buildArch() string {
  587. os := goos
  588. if os == "darwin" {
  589. os = "macosx"
  590. }
  591. return fmt.Sprintf("%s-%s", os, goarch)
  592. }
  593. func archiveName(target target) string {
  594. return fmt.Sprintf("%s-%s-%s", target.name, buildArch(), version)
  595. }
  596. func run(cmd string, args ...string) []byte {
  597. bs, err := runError(cmd, args...)
  598. if err != nil {
  599. log.Println(cmd, strings.Join(args, " "))
  600. log.Println(string(bs))
  601. log.Fatal(err)
  602. }
  603. return bytes.TrimSpace(bs)
  604. }
  605. func runError(cmd string, args ...string) ([]byte, error) {
  606. ecmd := exec.Command(cmd, args...)
  607. bs, err := ecmd.CombinedOutput()
  608. return bytes.TrimSpace(bs), err
  609. }
  610. func runPrint(cmd string, args ...string) {
  611. log.Println(cmd, strings.Join(args, " "))
  612. ecmd := exec.Command(cmd, args...)
  613. ecmd.Stdout = os.Stdout
  614. ecmd.Stderr = os.Stderr
  615. err := ecmd.Run()
  616. if err != nil {
  617. log.Fatal(err)
  618. }
  619. }
  620. func runPipe(file, cmd string, args ...string) {
  621. log.Println(cmd, strings.Join(args, " "), ">", file)
  622. fd, err := os.Create(file)
  623. if err != nil {
  624. log.Fatal(err)
  625. }
  626. ecmd := exec.Command(cmd, args...)
  627. ecmd.Stdout = fd
  628. ecmd.Stderr = os.Stderr
  629. err = ecmd.Run()
  630. if err != nil {
  631. log.Fatal(err)
  632. }
  633. fd.Close()
  634. }
  635. func tarGz(out string, files []archiveFile) {
  636. fd, err := os.Create(out)
  637. if err != nil {
  638. log.Fatal(err)
  639. }
  640. gw := gzip.NewWriter(fd)
  641. tw := tar.NewWriter(gw)
  642. for _, f := range files {
  643. sf, err := os.Open(f.src)
  644. if err != nil {
  645. log.Fatal(err)
  646. }
  647. info, err := sf.Stat()
  648. if err != nil {
  649. log.Fatal(err)
  650. }
  651. h := &tar.Header{
  652. Name: f.dst,
  653. Size: info.Size(),
  654. Mode: int64(info.Mode()),
  655. ModTime: info.ModTime(),
  656. }
  657. err = tw.WriteHeader(h)
  658. if err != nil {
  659. log.Fatal(err)
  660. }
  661. _, err = io.Copy(tw, sf)
  662. if err != nil {
  663. log.Fatal(err)
  664. }
  665. sf.Close()
  666. }
  667. err = tw.Close()
  668. if err != nil {
  669. log.Fatal(err)
  670. }
  671. err = gw.Close()
  672. if err != nil {
  673. log.Fatal(err)
  674. }
  675. err = fd.Close()
  676. if err != nil {
  677. log.Fatal(err)
  678. }
  679. }
  680. func zipFile(out string, files []archiveFile) {
  681. fd, err := os.Create(out)
  682. if err != nil {
  683. log.Fatal(err)
  684. }
  685. zw := zip.NewWriter(fd)
  686. for _, f := range files {
  687. sf, err := os.Open(f.src)
  688. if err != nil {
  689. log.Fatal(err)
  690. }
  691. info, err := sf.Stat()
  692. if err != nil {
  693. log.Fatal(err)
  694. }
  695. fh, err := zip.FileInfoHeader(info)
  696. if err != nil {
  697. log.Fatal(err)
  698. }
  699. fh.Name = f.dst
  700. fh.Method = zip.Deflate
  701. if strings.HasSuffix(f.dst, ".txt") {
  702. // Text file. Read it and convert line endings.
  703. bs, err := ioutil.ReadAll(sf)
  704. if err != nil {
  705. log.Fatal(err)
  706. }
  707. bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\n', '\r'}, -1)
  708. fh.UncompressedSize = uint32(len(bs))
  709. fh.UncompressedSize64 = uint64(len(bs))
  710. of, err := zw.CreateHeader(fh)
  711. if err != nil {
  712. log.Fatal(err)
  713. }
  714. of.Write(bs)
  715. } else {
  716. // Binary file. Copy verbatim.
  717. of, err := zw.CreateHeader(fh)
  718. if err != nil {
  719. log.Fatal(err)
  720. }
  721. _, err = io.Copy(of, sf)
  722. if err != nil {
  723. log.Fatal(err)
  724. }
  725. }
  726. }
  727. err = zw.Close()
  728. if err != nil {
  729. log.Fatal(err)
  730. }
  731. err = fd.Close()
  732. if err != nil {
  733. log.Fatal(err)
  734. }
  735. }
  736. func vet(pkg string) {
  737. bs, err := runError("go", "vet", pkg)
  738. if err != nil && err.Error() == "exit status 3" || bytes.Contains(bs, []byte("no such tool \"vet\"")) {
  739. // Go said there is no go vet
  740. log.Println(`- No go vet, no vetting. Try "go get -u golang.org/x/tools/cmd/vet".`)
  741. return
  742. }
  743. falseAlarmComposites := regexp.MustCompile("composite literal uses unkeyed fields")
  744. exitStatus := regexp.MustCompile("exit status 1")
  745. for _, line := range bytes.Split(bs, []byte("\n")) {
  746. if falseAlarmComposites.Match(line) || exitStatus.Match(line) {
  747. continue
  748. }
  749. if len(line) > 0 {
  750. log.Printf("%s", line)
  751. }
  752. }
  753. }
  754. func lint(pkg string) {
  755. bs, err := runError("golint", pkg)
  756. if err != nil {
  757. log.Println(`- No golint, not linting. Try "go get -u github.com/golang/lint/golint".`)
  758. return
  759. }
  760. analCommentPolicy := regexp.MustCompile(`exported (function|method|const|type|var) [^\s]+ should have comment`)
  761. for _, line := range bytes.Split(bs, []byte("\n")) {
  762. if analCommentPolicy.Match(line) {
  763. continue
  764. }
  765. if len(line) > 0 {
  766. log.Printf("%s", line)
  767. }
  768. }
  769. }
  770. func macosCodesign(file string) {
  771. if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
  772. bs, err := runError("security", "unlock-keychain", "-p", pass)
  773. if err != nil {
  774. log.Println("Codesign: unlocking keychain failed:", string(bs))
  775. return
  776. }
  777. }
  778. if id := os.Getenv("CODESIGN_IDENTITY"); id != "" {
  779. bs, err := runError("codesign", "-s", id, file)
  780. if err != nil {
  781. log.Println("Codesign: signing failed:", string(bs))
  782. return
  783. }
  784. log.Println("Codesign: successfully signed", file)
  785. }
  786. }