build.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  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. "errors"
  14. "flag"
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "log"
  19. "os"
  20. "os/exec"
  21. "os/user"
  22. "path/filepath"
  23. "regexp"
  24. "runtime"
  25. "strconv"
  26. "strings"
  27. "syscall"
  28. "text/template"
  29. "time"
  30. )
  31. var (
  32. versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
  33. goarch string
  34. goos string
  35. noupgrade bool
  36. version string
  37. goVersion float64
  38. race bool
  39. debug = os.Getenv("BUILDDEBUG") != ""
  40. )
  41. type target struct {
  42. name string
  43. buildPkg string
  44. binaryName string
  45. archiveFiles []archiveFile
  46. installationFiles []archiveFile
  47. tags []string
  48. }
  49. type archiveFile struct {
  50. src string
  51. dst string
  52. perm os.FileMode
  53. }
  54. var targets = map[string]target{
  55. "all": {
  56. // Only valid for the "build" and "install" commands as it lacks all
  57. // the archive creation stuff.
  58. buildPkg: "./cmd/...",
  59. tags: []string{"purego"},
  60. },
  61. "syncthing": {
  62. // The default target for "build", "install", "tar", "zip", "deb", etc.
  63. name: "syncthing",
  64. buildPkg: "./cmd/syncthing",
  65. binaryName: "syncthing", // .exe will be added automatically for Windows builds
  66. archiveFiles: []archiveFile{
  67. {src: "{{binary}}", dst: "{{binary}}", perm: 0755},
  68. {src: "README.md", dst: "README.txt", perm: 0644},
  69. {src: "LICENSE", dst: "LICENSE.txt", perm: 0644},
  70. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
  71. // All files from etc/ and extra/ added automatically in init().
  72. },
  73. installationFiles: []archiveFile{
  74. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
  75. {src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0644},
  76. {src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0644},
  77. {src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0644},
  78. {src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0644},
  79. {src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0644},
  80. {src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0644},
  81. {src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0644},
  82. {src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0644},
  83. {src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0644},
  84. {src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0644},
  85. {src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0644},
  86. {src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0644},
  87. {src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0644},
  88. {src: "etc/linux-systemd/system/[email protected]", dst: "deb/lib/systemd/system/[email protected]", perm: 0644},
  89. {src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
  90. {src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
  91. {src: "etc/firewall-ufw/syncthing", dst: "deb/etc/ufw/applications.d/syncthing", perm: 0644},
  92. },
  93. },
  94. "stdiscosrv": {
  95. name: "stdiscosrv",
  96. buildPkg: "./cmd/stdiscosrv",
  97. binaryName: "stdiscosrv", // .exe will be added automatically for Windows builds
  98. archiveFiles: []archiveFile{
  99. {src: "{{binary}}", dst: "{{binary}}", perm: 0755},
  100. {src: "cmd/stdiscosrv/README.md", dst: "README.txt", perm: 0644},
  101. {src: "cmd/stdiscosrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
  102. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
  103. },
  104. installationFiles: []archiveFile{
  105. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
  106. {src: "cmd/stdiscosrv/README.md", dst: "deb/usr/share/doc/stdiscosrv/README.txt", perm: 0644},
  107. {src: "cmd/stdiscosrv/LICENSE", dst: "deb/usr/share/doc/stdiscosrv/LICENSE.txt", perm: 0644},
  108. {src: "AUTHORS", dst: "deb/usr/share/doc/stdiscosrv/AUTHORS.txt", perm: 0644},
  109. {src: "man/stdiscosrv.1", dst: "deb/usr/share/man/man1/stdiscosrv.1", perm: 0644},
  110. },
  111. tags: []string{"purego"},
  112. },
  113. "strelaysrv": {
  114. name: "strelaysrv",
  115. buildPkg: "./cmd/strelaysrv",
  116. binaryName: "strelaysrv", // .exe will be added automatically for Windows builds
  117. archiveFiles: []archiveFile{
  118. {src: "{{binary}}", dst: "{{binary}}", perm: 0755},
  119. {src: "cmd/strelaysrv/README.md", dst: "README.txt", perm: 0644},
  120. {src: "cmd/strelaysrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
  121. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
  122. },
  123. installationFiles: []archiveFile{
  124. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
  125. {src: "cmd/strelaysrv/README.md", dst: "deb/usr/share/doc/strelaysrv/README.txt", perm: 0644},
  126. {src: "cmd/strelaysrv/LICENSE", dst: "deb/usr/share/doc/strelaysrv/LICENSE.txt", perm: 0644},
  127. {src: "AUTHORS", dst: "deb/usr/share/doc/strelaysrv/AUTHORS.txt", perm: 0644},
  128. {src: "man/strelaysrv.1", dst: "deb/usr/share/man/man1/strelaysrv.1", perm: 0644},
  129. },
  130. },
  131. "strelaypoolsrv": {
  132. name: "strelaypoolsrv",
  133. buildPkg: "./cmd/strelaypoolsrv",
  134. binaryName: "strelaypoolsrv", // .exe will be added automatically for Windows builds
  135. archiveFiles: []archiveFile{
  136. {src: "{{binary}}", dst: "{{binary}}", perm: 0755},
  137. {src: "cmd/strelaypoolsrv/README.md", dst: "README.txt", perm: 0644},
  138. {src: "cmd/strelaypoolsrv/LICENSE", dst: "LICENSE.txt", perm: 0644},
  139. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0644},
  140. },
  141. installationFiles: []archiveFile{
  142. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0755},
  143. {src: "cmd/strelaypoolsrv/README.md", dst: "deb/usr/share/doc/relaysrv/README.txt", perm: 0644},
  144. {src: "cmd/strelaypoolsrv/LICENSE", dst: "deb/usr/share/doc/relaysrv/LICENSE.txt", perm: 0644},
  145. {src: "AUTHORS", dst: "deb/usr/share/doc/relaysrv/AUTHORS.txt", perm: 0644},
  146. },
  147. },
  148. }
  149. func init() {
  150. // The "syncthing" target includes a few more files found in the "etc"
  151. // and "extra" dirs.
  152. syncthingPkg := targets["syncthing"]
  153. for _, file := range listFiles("etc") {
  154. syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
  155. }
  156. for _, file := range listFiles("extra") {
  157. syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0644})
  158. }
  159. for _, file := range listFiles("extra") {
  160. syncthingPkg.installationFiles = append(syncthingPkg.installationFiles, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0644})
  161. }
  162. targets["syncthing"] = syncthingPkg
  163. }
  164. const minGoVersion = 1.5
  165. func main() {
  166. log.SetOutput(os.Stdout)
  167. log.SetFlags(0)
  168. if debug {
  169. t0 := time.Now()
  170. defer func() {
  171. log.Println("... build completed in", time.Since(t0))
  172. }()
  173. }
  174. if os.Getenv("GOPATH") == "" {
  175. setGoPath()
  176. }
  177. // We use Go 1.5+ vendoring.
  178. os.Setenv("GO15VENDOREXPERIMENT", "1")
  179. // Set path to $GOPATH/bin:$PATH so that we can for sure find tools we
  180. // might have installed during "build.go setup".
  181. os.Setenv("PATH", fmt.Sprintf("%s%cbin%c%s", os.Getenv("GOPATH"), os.PathSeparator, os.PathListSeparator, os.Getenv("PATH")))
  182. parseFlags()
  183. checkArchitecture()
  184. goVersion, _ = checkRequiredGoVersion()
  185. // Invoking build.go with no parameters at all builds everything (incrementally),
  186. // which is what you want for maximum error checking during development.
  187. if flag.NArg() == 0 {
  188. runCommand("install", targets["all"])
  189. runCommand("vet", target{})
  190. runCommand("lint", target{})
  191. } else {
  192. // with any command given but not a target, the target is
  193. // "syncthing". So "go run build.go install" is "go run build.go install
  194. // syncthing" etc.
  195. targetName := "syncthing"
  196. if flag.NArg() > 1 {
  197. targetName = flag.Arg(1)
  198. }
  199. target, ok := targets[targetName]
  200. if !ok {
  201. log.Fatalln("Unknown target", target)
  202. }
  203. runCommand(flag.Arg(0), target)
  204. }
  205. }
  206. func checkArchitecture() {
  207. switch goarch {
  208. case "386", "amd64", "arm", "arm64", "ppc64", "ppc64le":
  209. break
  210. default:
  211. log.Printf("Unknown goarch %q; proceed with caution!", goarch)
  212. }
  213. }
  214. func runCommand(cmd string, target target) {
  215. switch cmd {
  216. case "setup":
  217. setup()
  218. case "install":
  219. var tags []string
  220. if noupgrade {
  221. tags = []string{"noupgrade"}
  222. }
  223. install(target, tags)
  224. case "build":
  225. var tags []string
  226. if noupgrade {
  227. tags = []string{"noupgrade"}
  228. }
  229. build(target, tags)
  230. case "test":
  231. test("./lib/...", "./cmd/...")
  232. case "bench":
  233. bench("./lib/...", "./cmd/...")
  234. case "assets":
  235. rebuildAssets()
  236. case "proto":
  237. proto()
  238. case "translate":
  239. translate()
  240. case "transifex":
  241. transifex()
  242. case "tar":
  243. buildTar(target)
  244. case "zip":
  245. buildZip(target)
  246. case "deb":
  247. buildDeb(target)
  248. case "snap":
  249. buildSnap(target)
  250. case "clean":
  251. clean()
  252. case "vet":
  253. vet("build.go")
  254. vet("cmd", "lib")
  255. case "lint":
  256. lint(".")
  257. lint("./cmd/...")
  258. lint("./lib/...")
  259. case "metalint":
  260. if isGometalinterInstalled() {
  261. dirs := []string{".", "./cmd/...", "./lib/..."}
  262. ok := gometalinter("deadcode", dirs, "test/util.go")
  263. ok = gometalinter("structcheck", dirs) && ok
  264. ok = gometalinter("varcheck", dirs) && ok
  265. ok = gometalinter("ineffassign", dirs) && ok
  266. if !ok {
  267. os.Exit(1)
  268. }
  269. }
  270. case "version":
  271. fmt.Println(getVersion())
  272. default:
  273. log.Fatalf("Unknown command %q", cmd)
  274. }
  275. }
  276. // setGoPath sets GOPATH correctly with the assumption that we are
  277. // in $GOPATH/src/github.com/syncthing/syncthing.
  278. func setGoPath() {
  279. cwd, err := os.Getwd()
  280. if err != nil {
  281. log.Fatal(err)
  282. }
  283. gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
  284. log.Println("GOPATH is", gopath)
  285. os.Setenv("GOPATH", gopath)
  286. }
  287. func parseFlags() {
  288. flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
  289. flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
  290. flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
  291. flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
  292. flag.BoolVar(&race, "race", race, "Use race detector")
  293. flag.Parse()
  294. }
  295. func checkRequiredGoVersion() (float64, bool) {
  296. re := regexp.MustCompile(`go(\d+\.\d+)`)
  297. ver := runtime.Version()
  298. if m := re.FindStringSubmatch(ver); len(m) == 2 {
  299. vs := string(m[1])
  300. // This is a standard go build. Verify that it's new enough.
  301. f, err := strconv.ParseFloat(vs, 64)
  302. if err != nil {
  303. log.Printf("*** Couldn't parse Go version out of %q.\n*** This isn't known to work, proceed on your own risk.", vs)
  304. return 0, false
  305. }
  306. if f < 1.5 {
  307. log.Printf("*** Go version %.01f doesn't support the vendoring mechanism.\n*** Ensure correct dependencies in your $GOPATH.", f)
  308. } else if f < minGoVersion {
  309. log.Fatalf("*** Go version %.01f is less than required %.01f.\n*** This is known not to work, not proceeding.", f, minGoVersion)
  310. }
  311. return f, true
  312. }
  313. log.Printf("*** Unknown Go version %q.\n*** This isn't known to work, proceed on your own risk.", ver)
  314. return 0, false
  315. }
  316. func setup() {
  317. packages := []string{
  318. "github.com/alecthomas/gometalinter",
  319. "github.com/AlekSi/gocov-xml",
  320. "github.com/axw/gocov/gocov",
  321. "github.com/FiloSottile/gvt",
  322. "github.com/golang/lint/golint",
  323. "github.com/gordonklaus/ineffassign",
  324. "github.com/mitchellh/go-wordwrap",
  325. "github.com/opennota/check/cmd/...",
  326. "github.com/tsenart/deadcode",
  327. "golang.org/x/net/html",
  328. "golang.org/x/tools/cmd/cover",
  329. }
  330. for _, pkg := range packages {
  331. fmt.Println(pkg)
  332. runPrint("go", "get", "-u", pkg)
  333. }
  334. }
  335. func test(pkgs ...string) {
  336. lazyRebuildAssets()
  337. useRace := runtime.GOARCH == "amd64"
  338. switch runtime.GOOS {
  339. case "darwin", "linux", "freebsd", "windows":
  340. default:
  341. useRace = false
  342. }
  343. if useRace {
  344. runPrint("go", append([]string{"test", "-short", "-race", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
  345. } else {
  346. runPrint("go", append([]string{"test", "-short", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
  347. }
  348. }
  349. func bench(pkgs ...string) {
  350. lazyRebuildAssets()
  351. runPrint("go", append([]string{"test", "-run", "NONE", "-bench", "."}, pkgs...)...)
  352. }
  353. func install(target target, tags []string) {
  354. lazyRebuildAssets()
  355. tags = append(target.tags, tags...)
  356. cwd, err := os.Getwd()
  357. if err != nil {
  358. log.Fatal(err)
  359. }
  360. os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
  361. args := []string{"install", "-v", "-ldflags", ldflags()}
  362. if len(tags) > 0 {
  363. args = append(args, "-tags", strings.Join(tags, " "))
  364. }
  365. if race {
  366. args = append(args, "-race")
  367. }
  368. args = append(args, target.buildPkg)
  369. os.Setenv("GOOS", goos)
  370. os.Setenv("GOARCH", goarch)
  371. runPrint("go", args...)
  372. }
  373. func build(target target, tags []string) {
  374. lazyRebuildAssets()
  375. tags = append(target.tags, tags...)
  376. rmr(target.binaryName)
  377. args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
  378. if len(tags) > 0 {
  379. args = append(args, "-tags", strings.Join(tags, " "))
  380. }
  381. if race {
  382. args = append(args, "-race")
  383. }
  384. args = append(args, target.buildPkg)
  385. os.Setenv("GOOS", goos)
  386. os.Setenv("GOARCH", goarch)
  387. runPrint("go", args...)
  388. }
  389. func buildTar(target target) {
  390. name := archiveName(target)
  391. filename := name + ".tar.gz"
  392. var tags []string
  393. if noupgrade {
  394. tags = []string{"noupgrade"}
  395. name += "-noupgrade"
  396. }
  397. build(target, tags)
  398. if goos == "darwin" {
  399. macosCodesign(target.binaryName)
  400. }
  401. for i := range target.archiveFiles {
  402. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
  403. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
  404. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  405. }
  406. tarGz(filename, target.archiveFiles)
  407. log.Println(filename)
  408. }
  409. func buildZip(target target) {
  410. target.binaryName += ".exe"
  411. name := archiveName(target)
  412. filename := name + ".zip"
  413. var tags []string
  414. if noupgrade {
  415. tags = []string{"noupgrade"}
  416. name += "-noupgrade"
  417. }
  418. build(target, tags)
  419. for i := range target.archiveFiles {
  420. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
  421. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
  422. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  423. }
  424. zipFile(filename, target.archiveFiles)
  425. log.Println(filename)
  426. }
  427. func buildDeb(target target) {
  428. os.RemoveAll("deb")
  429. // "goarch" here is set to whatever the Debian packages expect. We correct
  430. // it to what we actually know how to build and keep the Debian variant
  431. // name in "debarch".
  432. debarch := goarch
  433. switch goarch {
  434. case "i386":
  435. goarch = "386"
  436. case "armel", "armhf":
  437. goarch = "arm"
  438. }
  439. build(target, []string{"noupgrade"})
  440. for i := range target.installationFiles {
  441. target.installationFiles[i].src = strings.Replace(target.installationFiles[i].src, "{{binary}}", target.binaryName, 1)
  442. target.installationFiles[i].dst = strings.Replace(target.installationFiles[i].dst, "{{binary}}", target.binaryName, 1)
  443. }
  444. for _, af := range target.installationFiles {
  445. if err := copyFile(af.src, af.dst, af.perm); err != nil {
  446. log.Fatal(err)
  447. }
  448. }
  449. maintainer := "Syncthing Release Management <[email protected]>"
  450. debver := version
  451. if strings.HasPrefix(debver, "v") {
  452. debver = debver[1:]
  453. }
  454. runPrint("fpm", "-t", "deb", "-s", "dir", "-C", "deb",
  455. "-n", "syncthing", "-v", debver, "-a", debarch,
  456. "--vendor", maintainer, "-m", maintainer,
  457. "-d", "libc6",
  458. "-d", "procps", // because postinst script
  459. "--url", "https://syncthing.net/",
  460. "--description", "Open Source Continuous File Synchronization",
  461. "--after-upgrade", "script/post-upgrade",
  462. "--license", "MPL-2")
  463. }
  464. func buildSnap(target target) {
  465. os.RemoveAll("snap")
  466. tmpl, err := template.ParseFiles("snapcraft.yaml.template")
  467. if err != nil {
  468. log.Fatal(err)
  469. }
  470. f, err := os.Create("snapcraft.yaml")
  471. defer f.Close()
  472. if err != nil {
  473. log.Fatal(err)
  474. }
  475. snaparch := goarch
  476. if snaparch == "armhf" {
  477. goarch = "arm"
  478. }
  479. snapver := version
  480. if strings.HasPrefix(snapver, "v") {
  481. snapver = snapver[1:]
  482. }
  483. snapgrade := "devel"
  484. if matched, _ := regexp.MatchString(`^\d+\.\d+\.\d+$`, snapver); matched {
  485. snapgrade = "stable"
  486. }
  487. err = tmpl.Execute(f, map[string]string{
  488. "Version": snapver,
  489. "Architecture": snaparch,
  490. "Grade": snapgrade,
  491. })
  492. if err != nil {
  493. log.Fatal(err)
  494. }
  495. runPrint("snapcraft", "clean")
  496. build(target, []string{"noupgrade"})
  497. runPrint("snapcraft")
  498. }
  499. func copyFile(src, dst string, perm os.FileMode) error {
  500. dstDir := filepath.Dir(dst)
  501. os.MkdirAll(dstDir, 0755) // ignore error
  502. srcFd, err := os.Open(src)
  503. if err != nil {
  504. return err
  505. }
  506. defer srcFd.Close()
  507. dstFd, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
  508. if err != nil {
  509. return err
  510. }
  511. defer dstFd.Close()
  512. _, err = io.Copy(dstFd, srcFd)
  513. return err
  514. }
  515. func listFiles(dir string) []string {
  516. var res []string
  517. filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  518. if err != nil {
  519. return err
  520. }
  521. if fi.Mode().IsRegular() {
  522. res = append(res, path)
  523. }
  524. return nil
  525. })
  526. return res
  527. }
  528. func rebuildAssets() {
  529. runPipe("lib/auto/gui.files.go", "go", "run", "script/genassets.go", "gui")
  530. runPipe("cmd/strelaypoolsrv/auto/gui.go", "go", "run", "script/genassets.go", "cmd/strelaypoolsrv/gui")
  531. }
  532. func lazyRebuildAssets() {
  533. if shouldRebuildAssets("lib/auto/gui.files.go", "gui") || shouldRebuildAssets("cmd/strelaypoolsrv/auto/gui.go", "cmd/strelaypoolsrv/auto/gui") {
  534. rebuildAssets()
  535. }
  536. }
  537. func shouldRebuildAssets(target, srcdir string) bool {
  538. info, err := os.Stat(target)
  539. if err != nil {
  540. // If the file doesn't exist, we must rebuild it
  541. return true
  542. }
  543. // Check if any of the files in gui/ are newer than the asset file. If
  544. // so we should rebuild it.
  545. currentBuild := info.ModTime()
  546. assetsAreNewer := false
  547. stop := errors.New("no need to iterate further")
  548. filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
  549. if err != nil {
  550. return err
  551. }
  552. if info.ModTime().After(currentBuild) {
  553. assetsAreNewer = true
  554. return stop
  555. }
  556. return nil
  557. })
  558. return assetsAreNewer
  559. }
  560. func proto() {
  561. runPrint("go", "generate", "./lib/...")
  562. }
  563. func translate() {
  564. os.Chdir("gui/default/assets/lang")
  565. runPipe("lang-en-new.json", "go", "run", "../../../../script/translate.go", "lang-en.json", "../../../")
  566. os.Remove("lang-en.json")
  567. err := os.Rename("lang-en-new.json", "lang-en.json")
  568. if err != nil {
  569. log.Fatal(err)
  570. }
  571. os.Chdir("../../../..")
  572. }
  573. func transifex() {
  574. os.Chdir("gui/default/assets/lang")
  575. runPrint("go", "run", "../../../../script/transifexdl.go")
  576. }
  577. func clean() {
  578. rmr("bin")
  579. rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/syncthing", goos, goarch)))
  580. }
  581. func ldflags() string {
  582. sep := '='
  583. if goVersion > 0 && goVersion < 1.5 {
  584. sep = ' '
  585. }
  586. b := new(bytes.Buffer)
  587. b.WriteString("-w")
  588. fmt.Fprintf(b, " -X main.Version%c%s", sep, version)
  589. fmt.Fprintf(b, " -X main.BuildStamp%c%d", sep, buildStamp())
  590. fmt.Fprintf(b, " -X main.BuildUser%c%s", sep, buildUser())
  591. fmt.Fprintf(b, " -X main.BuildHost%c%s", sep, buildHost())
  592. return b.String()
  593. }
  594. func rmr(paths ...string) {
  595. for _, path := range paths {
  596. if debug {
  597. log.Println("rm -r", path)
  598. }
  599. os.RemoveAll(path)
  600. }
  601. }
  602. func getReleaseVersion() (string, error) {
  603. fd, err := os.Open("RELEASE")
  604. if err != nil {
  605. return "", err
  606. }
  607. defer fd.Close()
  608. bs, err := ioutil.ReadAll(fd)
  609. if err != nil {
  610. return "", err
  611. }
  612. return string(bytes.TrimSpace(bs)), nil
  613. }
  614. func getGitVersion() (string, error) {
  615. v, err := runError("git", "describe", "--always", "--dirty")
  616. if err != nil {
  617. return "", err
  618. }
  619. v = versionRe.ReplaceAllFunc(v, func(s []byte) []byte {
  620. s[0] = '+'
  621. return s
  622. })
  623. return string(v), nil
  624. }
  625. func getVersion() string {
  626. // First try for a RELEASE file,
  627. if ver, err := getReleaseVersion(); err == nil {
  628. return ver
  629. }
  630. // ... then see if we have a Git tag.
  631. if ver, err := getGitVersion(); err == nil {
  632. if strings.Contains(ver, "-") {
  633. // The version already contains a hash and stuff. See if we can
  634. // find a current branch name to tack onto it as well.
  635. return ver + getBranchSuffix()
  636. }
  637. return ver
  638. }
  639. // This seems to be a dev build.
  640. return "unknown-dev"
  641. }
  642. func getBranchSuffix() string {
  643. bs, err := runError("git", "branch", "-a", "--contains")
  644. if err != nil {
  645. return ""
  646. }
  647. branches := strings.Split(string(bs), "\n")
  648. if len(branches) == 0 {
  649. return ""
  650. }
  651. branch := ""
  652. for i, candidate := range branches {
  653. if strings.HasPrefix(candidate, "*") {
  654. // This is the current branch. Select it!
  655. branch = strings.TrimLeft(candidate, " \t*")
  656. break
  657. } else if i == 0 {
  658. // Otherwise the first branch in the list will do.
  659. branch = strings.TrimSpace(branch)
  660. }
  661. }
  662. if branch == "" {
  663. return ""
  664. }
  665. // The branch name may be on the form "remotes/origin/foo" from which we
  666. // just want "foo".
  667. parts := strings.Split(branch, "/")
  668. if len(parts) == 0 || len(parts[len(parts)-1]) == 0 {
  669. return ""
  670. }
  671. branch = parts[len(parts)-1]
  672. if branch == "master" {
  673. // master builds are the default.
  674. return ""
  675. }
  676. validBranchRe := regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
  677. if !validBranchRe.MatchString(branch) {
  678. // There's some odd stuff in the branch name. Better skip it.
  679. return ""
  680. }
  681. return "-" + branch
  682. }
  683. func buildStamp() int64 {
  684. // If SOURCE_DATE_EPOCH is set, use that.
  685. if s, _ := strconv.ParseInt(os.Getenv("SOURCE_DATE_EPOCH"), 10, 64); s > 0 {
  686. return s
  687. }
  688. // Try to get the timestamp of the latest commit.
  689. bs, err := runError("git", "show", "-s", "--format=%ct")
  690. if err != nil {
  691. // Fall back to "now".
  692. return time.Now().Unix()
  693. }
  694. s, _ := strconv.ParseInt(string(bs), 10, 64)
  695. return s
  696. }
  697. func buildUser() string {
  698. if v := os.Getenv("BUILD_USER"); v != "" {
  699. return v
  700. }
  701. u, err := user.Current()
  702. if err != nil {
  703. return "unknown-user"
  704. }
  705. return strings.Replace(u.Username, " ", "-", -1)
  706. }
  707. func buildHost() string {
  708. if v := os.Getenv("BUILD_HOST"); v != "" {
  709. return v
  710. }
  711. h, err := os.Hostname()
  712. if err != nil {
  713. return "unknown-host"
  714. }
  715. return h
  716. }
  717. func buildArch() string {
  718. os := goos
  719. if os == "darwin" {
  720. os = "macosx"
  721. }
  722. return fmt.Sprintf("%s-%s", os, goarch)
  723. }
  724. func archiveName(target target) string {
  725. return fmt.Sprintf("%s-%s-%s", target.name, buildArch(), version)
  726. }
  727. func runError(cmd string, args ...string) ([]byte, error) {
  728. if debug {
  729. t0 := time.Now()
  730. log.Println("runError:", cmd, strings.Join(args, " "))
  731. defer func() {
  732. log.Println("... in", time.Since(t0))
  733. }()
  734. }
  735. ecmd := exec.Command(cmd, args...)
  736. bs, err := ecmd.CombinedOutput()
  737. return bytes.TrimSpace(bs), err
  738. }
  739. func runPrint(cmd string, args ...string) {
  740. if debug {
  741. t0 := time.Now()
  742. log.Println("runPrint:", cmd, strings.Join(args, " "))
  743. defer func() {
  744. log.Println("... in", time.Since(t0))
  745. }()
  746. }
  747. ecmd := exec.Command(cmd, args...)
  748. ecmd.Stdout = os.Stdout
  749. ecmd.Stderr = os.Stderr
  750. err := ecmd.Run()
  751. if err != nil {
  752. log.Fatal(err)
  753. }
  754. }
  755. func runPipe(file, cmd string, args ...string) {
  756. if debug {
  757. t0 := time.Now()
  758. log.Println("runPipe:", cmd, strings.Join(args, " "))
  759. defer func() {
  760. log.Println("... in", time.Since(t0))
  761. }()
  762. }
  763. fd, err := os.Create(file)
  764. if err != nil {
  765. log.Fatal(err)
  766. }
  767. ecmd := exec.Command(cmd, args...)
  768. ecmd.Stdout = fd
  769. ecmd.Stderr = os.Stderr
  770. err = ecmd.Run()
  771. if err != nil {
  772. log.Fatal(err)
  773. }
  774. fd.Close()
  775. }
  776. func tarGz(out string, files []archiveFile) {
  777. fd, err := os.Create(out)
  778. if err != nil {
  779. log.Fatal(err)
  780. }
  781. gw := gzip.NewWriter(fd)
  782. tw := tar.NewWriter(gw)
  783. for _, f := range files {
  784. sf, err := os.Open(f.src)
  785. if err != nil {
  786. log.Fatal(err)
  787. }
  788. info, err := sf.Stat()
  789. if err != nil {
  790. log.Fatal(err)
  791. }
  792. h := &tar.Header{
  793. Name: f.dst,
  794. Size: info.Size(),
  795. Mode: int64(info.Mode()),
  796. ModTime: info.ModTime(),
  797. }
  798. err = tw.WriteHeader(h)
  799. if err != nil {
  800. log.Fatal(err)
  801. }
  802. _, err = io.Copy(tw, sf)
  803. if err != nil {
  804. log.Fatal(err)
  805. }
  806. sf.Close()
  807. }
  808. err = tw.Close()
  809. if err != nil {
  810. log.Fatal(err)
  811. }
  812. err = gw.Close()
  813. if err != nil {
  814. log.Fatal(err)
  815. }
  816. err = fd.Close()
  817. if err != nil {
  818. log.Fatal(err)
  819. }
  820. }
  821. func zipFile(out string, files []archiveFile) {
  822. fd, err := os.Create(out)
  823. if err != nil {
  824. log.Fatal(err)
  825. }
  826. zw := zip.NewWriter(fd)
  827. for _, f := range files {
  828. sf, err := os.Open(f.src)
  829. if err != nil {
  830. log.Fatal(err)
  831. }
  832. info, err := sf.Stat()
  833. if err != nil {
  834. log.Fatal(err)
  835. }
  836. fh, err := zip.FileInfoHeader(info)
  837. if err != nil {
  838. log.Fatal(err)
  839. }
  840. fh.Name = filepath.ToSlash(f.dst)
  841. fh.Method = zip.Deflate
  842. if strings.HasSuffix(f.dst, ".txt") {
  843. // Text file. Read it and convert line endings.
  844. bs, err := ioutil.ReadAll(sf)
  845. if err != nil {
  846. log.Fatal(err)
  847. }
  848. bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\n', '\r'}, -1)
  849. fh.UncompressedSize = uint32(len(bs))
  850. fh.UncompressedSize64 = uint64(len(bs))
  851. of, err := zw.CreateHeader(fh)
  852. if err != nil {
  853. log.Fatal(err)
  854. }
  855. of.Write(bs)
  856. } else {
  857. // Binary file. Copy verbatim.
  858. of, err := zw.CreateHeader(fh)
  859. if err != nil {
  860. log.Fatal(err)
  861. }
  862. _, err = io.Copy(of, sf)
  863. if err != nil {
  864. log.Fatal(err)
  865. }
  866. }
  867. }
  868. err = zw.Close()
  869. if err != nil {
  870. log.Fatal(err)
  871. }
  872. err = fd.Close()
  873. if err != nil {
  874. log.Fatal(err)
  875. }
  876. }
  877. func vet(dirs ...string) {
  878. params := []string{"tool", "vet", "-all"}
  879. params = append(params, dirs...)
  880. bs, err := runError("go", params...)
  881. if len(bs) > 0 {
  882. log.Printf("%s", bs)
  883. }
  884. if err != nil {
  885. if exitStatus(err) == 3 {
  886. // Exit code 3, the "vet" tool is not installed
  887. return
  888. }
  889. // A genuine error exit from the vet tool.
  890. log.Fatal(err)
  891. }
  892. }
  893. func lint(pkg string) {
  894. bs, err := runError("golint", pkg)
  895. if err != nil {
  896. log.Println(`- No golint, not linting. Try "go get -u github.com/golang/lint/golint".`)
  897. return
  898. }
  899. analCommentPolicy := regexp.MustCompile(`exported (function|method|const|type|var) [^\s]+ should have comment`)
  900. for _, line := range strings.Split(string(bs), "\n") {
  901. if line == "" {
  902. continue
  903. }
  904. if analCommentPolicy.MatchString(line) {
  905. continue
  906. }
  907. if strings.Contains(line, ".pb.go:") {
  908. continue
  909. }
  910. log.Println(line)
  911. }
  912. }
  913. func macosCodesign(file string) {
  914. if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
  915. bs, err := runError("security", "unlock-keychain", "-p", pass)
  916. if err != nil {
  917. log.Println("Codesign: unlocking keychain failed:", string(bs))
  918. return
  919. }
  920. }
  921. if id := os.Getenv("CODESIGN_IDENTITY"); id != "" {
  922. bs, err := runError("codesign", "-s", id, file)
  923. if err != nil {
  924. log.Println("Codesign: signing failed:", string(bs))
  925. return
  926. }
  927. log.Println("Codesign: successfully signed", file)
  928. }
  929. }
  930. func exitStatus(err error) int {
  931. if err, ok := err.(*exec.ExitError); ok {
  932. if ws, ok := err.ProcessState.Sys().(syscall.WaitStatus); ok {
  933. return ws.ExitStatus()
  934. }
  935. }
  936. return -1
  937. }
  938. func isGometalinterInstalled() bool {
  939. if _, err := runError("gometalinter", "--disable-all"); err != nil {
  940. log.Println("gometalinter is not installed")
  941. return false
  942. }
  943. return true
  944. }
  945. func gometalinter(linter string, dirs []string, excludes ...string) bool {
  946. params := []string{"--disable-all"}
  947. params = append(params, fmt.Sprintf("--deadline=%ds", 60))
  948. params = append(params, "--enable="+linter)
  949. for _, exclude := range excludes {
  950. params = append(params, "--exclude="+exclude)
  951. }
  952. for _, dir := range dirs {
  953. params = append(params, dir)
  954. }
  955. bs, _ := runError("gometalinter", params...)
  956. nerr := 0
  957. for _, line := range strings.Split(string(bs), "\n") {
  958. if line == "" {
  959. continue
  960. }
  961. if strings.Contains(line, ".pb.go:") {
  962. continue
  963. }
  964. log.Println(line)
  965. nerr++
  966. }
  967. return nerr == 0
  968. }