build.go 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  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. if !ok {
  266. os.Exit(1)
  267. }
  268. }
  269. case "version":
  270. fmt.Println(getVersion())
  271. default:
  272. log.Fatalf("Unknown command %q", cmd)
  273. }
  274. }
  275. // setGoPath sets GOPATH correctly with the assumption that we are
  276. // in $GOPATH/src/github.com/syncthing/syncthing.
  277. func setGoPath() {
  278. cwd, err := os.Getwd()
  279. if err != nil {
  280. log.Fatal(err)
  281. }
  282. gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
  283. log.Println("GOPATH is", gopath)
  284. os.Setenv("GOPATH", gopath)
  285. }
  286. func parseFlags() {
  287. flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
  288. flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
  289. flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
  290. flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
  291. flag.BoolVar(&race, "race", race, "Use race detector")
  292. flag.Parse()
  293. }
  294. func checkRequiredGoVersion() (float64, bool) {
  295. re := regexp.MustCompile(`go(\d+\.\d+)`)
  296. ver := runtime.Version()
  297. if m := re.FindStringSubmatch(ver); len(m) == 2 {
  298. vs := string(m[1])
  299. // This is a standard go build. Verify that it's new enough.
  300. f, err := strconv.ParseFloat(vs, 64)
  301. if err != nil {
  302. log.Printf("*** Couldn't parse Go version out of %q.\n*** This isn't known to work, proceed on your own risk.", vs)
  303. return 0, false
  304. }
  305. if f < 1.5 {
  306. log.Printf("*** Go version %.01f doesn't support the vendoring mechanism.\n*** Ensure correct dependencies in your $GOPATH.", f)
  307. } else if f < minGoVersion {
  308. log.Fatalf("*** Go version %.01f is less than required %.01f.\n*** This is known not to work, not proceeding.", f, minGoVersion)
  309. }
  310. return f, true
  311. }
  312. log.Printf("*** Unknown Go version %q.\n*** This isn't known to work, proceed on your own risk.", ver)
  313. return 0, false
  314. }
  315. func setup() {
  316. runPrint("go", "get", "-v", "github.com/golang/lint/golint")
  317. runPrint("go", "get", "-v", "golang.org/x/tools/cmd/cover")
  318. runPrint("go", "get", "-v", "golang.org/x/net/html")
  319. runPrint("go", "get", "-v", "github.com/FiloSottile/gvt")
  320. runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
  321. runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
  322. runPrint("go", "get", "-v", "github.com/alecthomas/gometalinter")
  323. runPrint("go", "get", "-v", "github.com/mitchellh/go-wordwrap")
  324. }
  325. func test(pkgs ...string) {
  326. lazyRebuildAssets()
  327. useRace := runtime.GOARCH == "amd64"
  328. switch runtime.GOOS {
  329. case "darwin", "linux", "freebsd", "windows":
  330. default:
  331. useRace = false
  332. }
  333. if useRace {
  334. runPrint("go", append([]string{"test", "-short", "-race", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
  335. } else {
  336. runPrint("go", append([]string{"test", "-short", "-timeout", "60s", "-tags", "purego"}, pkgs...)...)
  337. }
  338. }
  339. func bench(pkgs ...string) {
  340. lazyRebuildAssets()
  341. runPrint("go", append([]string{"test", "-run", "NONE", "-bench", "."}, pkgs...)...)
  342. }
  343. func install(target target, tags []string) {
  344. lazyRebuildAssets()
  345. tags = append(target.tags, tags...)
  346. cwd, err := os.Getwd()
  347. if err != nil {
  348. log.Fatal(err)
  349. }
  350. os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
  351. args := []string{"install", "-v", "-ldflags", ldflags()}
  352. if len(tags) > 0 {
  353. args = append(args, "-tags", strings.Join(tags, " "))
  354. }
  355. if race {
  356. args = append(args, "-race")
  357. }
  358. args = append(args, target.buildPkg)
  359. os.Setenv("GOOS", goos)
  360. os.Setenv("GOARCH", goarch)
  361. runPrint("go", args...)
  362. }
  363. func build(target target, tags []string) {
  364. lazyRebuildAssets()
  365. tags = append(target.tags, tags...)
  366. rmr(target.binaryName)
  367. args := []string{"build", "-i", "-v", "-ldflags", ldflags()}
  368. if len(tags) > 0 {
  369. args = append(args, "-tags", strings.Join(tags, " "))
  370. }
  371. if race {
  372. args = append(args, "-race")
  373. }
  374. args = append(args, target.buildPkg)
  375. os.Setenv("GOOS", goos)
  376. os.Setenv("GOARCH", goarch)
  377. runPrint("go", args...)
  378. }
  379. func buildTar(target target) {
  380. name := archiveName(target)
  381. filename := name + ".tar.gz"
  382. var tags []string
  383. if noupgrade {
  384. tags = []string{"noupgrade"}
  385. name += "-noupgrade"
  386. }
  387. build(target, tags)
  388. if goos == "darwin" {
  389. macosCodesign(target.binaryName)
  390. }
  391. for i := range target.archiveFiles {
  392. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
  393. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
  394. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  395. }
  396. tarGz(filename, target.archiveFiles)
  397. log.Println(filename)
  398. }
  399. func buildZip(target target) {
  400. target.binaryName += ".exe"
  401. name := archiveName(target)
  402. filename := name + ".zip"
  403. var tags []string
  404. if noupgrade {
  405. tags = []string{"noupgrade"}
  406. name += "-noupgrade"
  407. }
  408. build(target, tags)
  409. for i := range target.archiveFiles {
  410. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.binaryName, 1)
  411. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.binaryName, 1)
  412. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  413. }
  414. zipFile(filename, target.archiveFiles)
  415. log.Println(filename)
  416. }
  417. func buildDeb(target target) {
  418. os.RemoveAll("deb")
  419. // "goarch" here is set to whatever the Debian packages expect. We correct
  420. // it to what we actually know how to build and keep the Debian variant
  421. // name in "debarch".
  422. debarch := goarch
  423. switch goarch {
  424. case "i386":
  425. goarch = "386"
  426. case "armel", "armhf":
  427. goarch = "arm"
  428. }
  429. build(target, []string{"noupgrade"})
  430. for i := range target.installationFiles {
  431. target.installationFiles[i].src = strings.Replace(target.installationFiles[i].src, "{{binary}}", target.binaryName, 1)
  432. target.installationFiles[i].dst = strings.Replace(target.installationFiles[i].dst, "{{binary}}", target.binaryName, 1)
  433. }
  434. for _, af := range target.installationFiles {
  435. if err := copyFile(af.src, af.dst, af.perm); err != nil {
  436. log.Fatal(err)
  437. }
  438. }
  439. maintainer := "Syncthing Release Management <[email protected]>"
  440. debver := version
  441. if strings.HasPrefix(debver, "v") {
  442. debver = debver[1:]
  443. }
  444. runPrint("fpm", "-t", "deb", "-s", "dir", "-C", "deb",
  445. "-n", "syncthing", "-v", debver, "-a", debarch,
  446. "--vendor", maintainer, "-m", maintainer,
  447. "-d", "libc6",
  448. "-d", "procps", // because postinst script
  449. "--url", "https://syncthing.net/",
  450. "--description", "Open Source Continuous File Synchronization",
  451. "--after-upgrade", "script/post-upgrade",
  452. "--license", "MPL-2")
  453. }
  454. func buildSnap(target target) {
  455. os.RemoveAll("snap")
  456. tmpl, err := template.ParseFiles("snapcraft.yaml.template")
  457. if err != nil {
  458. log.Fatal(err)
  459. }
  460. f, err := os.Create("snapcraft.yaml")
  461. defer f.Close()
  462. if err != nil {
  463. log.Fatal(err)
  464. }
  465. snaparch := goarch
  466. if snaparch == "armhf" {
  467. goarch = "arm"
  468. }
  469. err = tmpl.Execute(f, map[string]string{
  470. "Version": version,
  471. "Architecture": snaparch})
  472. if err != nil {
  473. log.Fatal(err)
  474. }
  475. runPrint("snapcraft", "clean")
  476. build(target, []string{"noupgrade"})
  477. runPrint("snapcraft")
  478. }
  479. func copyFile(src, dst string, perm os.FileMode) error {
  480. dstDir := filepath.Dir(dst)
  481. os.MkdirAll(dstDir, 0755) // ignore error
  482. srcFd, err := os.Open(src)
  483. if err != nil {
  484. return err
  485. }
  486. defer srcFd.Close()
  487. dstFd, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
  488. if err != nil {
  489. return err
  490. }
  491. defer dstFd.Close()
  492. _, err = io.Copy(dstFd, srcFd)
  493. return err
  494. }
  495. func listFiles(dir string) []string {
  496. var res []string
  497. filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  498. if err != nil {
  499. return err
  500. }
  501. if fi.Mode().IsRegular() {
  502. res = append(res, path)
  503. }
  504. return nil
  505. })
  506. return res
  507. }
  508. func rebuildAssets() {
  509. runPipe("lib/auto/gui.files.go", "go", "run", "script/genassets.go", "gui")
  510. runPipe("cmd/strelaypoolsrv/auto/gui.go", "go", "run", "script/genassets.go", "cmd/strelaypoolsrv/gui")
  511. }
  512. func lazyRebuildAssets() {
  513. if shouldRebuildAssets("lib/auto/gui.files.go", "gui") || shouldRebuildAssets("cmd/strelaypoolsrv/auto/gui.go", "cmd/strelaypoolsrv/auto/gui") {
  514. rebuildAssets()
  515. }
  516. }
  517. func shouldRebuildAssets(target, srcdir string) bool {
  518. info, err := os.Stat(target)
  519. if err != nil {
  520. // If the file doesn't exist, we must rebuild it
  521. return true
  522. }
  523. // Check if any of the files in gui/ are newer than the asset file. If
  524. // so we should rebuild it.
  525. currentBuild := info.ModTime()
  526. assetsAreNewer := false
  527. stop := errors.New("no need to iterate further")
  528. filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
  529. if err != nil {
  530. return err
  531. }
  532. if info.ModTime().After(currentBuild) {
  533. assetsAreNewer = true
  534. return stop
  535. }
  536. return nil
  537. })
  538. return assetsAreNewer
  539. }
  540. func proto() {
  541. runPrint("go", "generate", "./lib/...")
  542. }
  543. func translate() {
  544. os.Chdir("gui/default/assets/lang")
  545. runPipe("lang-en-new.json", "go", "run", "../../../../script/translate.go", "lang-en.json", "../../../")
  546. os.Remove("lang-en.json")
  547. err := os.Rename("lang-en-new.json", "lang-en.json")
  548. if err != nil {
  549. log.Fatal(err)
  550. }
  551. os.Chdir("../../../..")
  552. }
  553. func transifex() {
  554. os.Chdir("gui/default/assets/lang")
  555. runPrint("go", "run", "../../../../script/transifexdl.go")
  556. }
  557. func clean() {
  558. rmr("bin")
  559. rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/syncthing", goos, goarch)))
  560. }
  561. func ldflags() string {
  562. sep := '='
  563. if goVersion > 0 && goVersion < 1.5 {
  564. sep = ' '
  565. }
  566. b := new(bytes.Buffer)
  567. b.WriteString("-w")
  568. fmt.Fprintf(b, " -X main.Version%c%s", sep, version)
  569. fmt.Fprintf(b, " -X main.BuildStamp%c%d", sep, buildStamp())
  570. fmt.Fprintf(b, " -X main.BuildUser%c%s", sep, buildUser())
  571. fmt.Fprintf(b, " -X main.BuildHost%c%s", sep, buildHost())
  572. return b.String()
  573. }
  574. func rmr(paths ...string) {
  575. for _, path := range paths {
  576. if debug {
  577. log.Println("rm -r", path)
  578. }
  579. os.RemoveAll(path)
  580. }
  581. }
  582. func getReleaseVersion() (string, error) {
  583. fd, err := os.Open("RELEASE")
  584. if err != nil {
  585. return "", err
  586. }
  587. defer fd.Close()
  588. bs, err := ioutil.ReadAll(fd)
  589. if err != nil {
  590. return "", err
  591. }
  592. return string(bytes.TrimSpace(bs)), nil
  593. }
  594. func getGitVersion() (string, error) {
  595. v, err := runError("git", "describe", "--always", "--dirty")
  596. if err != nil {
  597. return "", err
  598. }
  599. v = versionRe.ReplaceAllFunc(v, func(s []byte) []byte {
  600. s[0] = '+'
  601. return s
  602. })
  603. return string(v), nil
  604. }
  605. func getVersion() string {
  606. // First try for a RELEASE file,
  607. if ver, err := getReleaseVersion(); err == nil {
  608. return ver
  609. }
  610. // ... then see if we have a Git tag.
  611. if ver, err := getGitVersion(); err == nil {
  612. if strings.Contains(ver, "-") {
  613. // The version already contains a hash and stuff. See if we can
  614. // find a current branch name to tack onto it as well.
  615. return ver + getBranchSuffix()
  616. }
  617. return ver
  618. }
  619. // This seems to be a dev build.
  620. return "unknown-dev"
  621. }
  622. func getBranchSuffix() string {
  623. bs, err := runError("git", "branch", "-a", "--contains")
  624. if err != nil {
  625. return ""
  626. }
  627. branches := strings.Split(string(bs), "\n")
  628. if len(branches) == 0 {
  629. return ""
  630. }
  631. branch := ""
  632. for i, candidate := range branches {
  633. if strings.HasPrefix(candidate, "*") {
  634. // This is the current branch. Select it!
  635. branch = strings.TrimLeft(candidate, " \t*")
  636. break
  637. } else if i == 0 {
  638. // Otherwise the first branch in the list will do.
  639. branch = strings.TrimSpace(branch)
  640. }
  641. }
  642. if branch == "" {
  643. return ""
  644. }
  645. // The branch name may be on the form "remotes/origin/foo" from which we
  646. // just want "foo".
  647. parts := strings.Split(branch, "/")
  648. if len(parts) == 0 || len(parts[len(parts)-1]) == 0 {
  649. return ""
  650. }
  651. branch = parts[len(parts)-1]
  652. if branch == "master" {
  653. // master builds are the default.
  654. return ""
  655. }
  656. validBranchRe := regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
  657. if !validBranchRe.MatchString(branch) {
  658. // There's some odd stuff in the branch name. Better skip it.
  659. return ""
  660. }
  661. return "-" + branch
  662. }
  663. func buildStamp() int64 {
  664. // If SOURCE_DATE_EPOCH is set, use that.
  665. if s, _ := strconv.ParseInt(os.Getenv("SOURCE_DATE_EPOCH"), 10, 64); s > 0 {
  666. return s
  667. }
  668. // Try to get the timestamp of the latest commit.
  669. bs, err := runError("git", "show", "-s", "--format=%ct")
  670. if err != nil {
  671. // Fall back to "now".
  672. return time.Now().Unix()
  673. }
  674. s, _ := strconv.ParseInt(string(bs), 10, 64)
  675. return s
  676. }
  677. func buildUser() string {
  678. if v := os.Getenv("BUILD_USER"); v != "" {
  679. return v
  680. }
  681. u, err := user.Current()
  682. if err != nil {
  683. return "unknown-user"
  684. }
  685. return strings.Replace(u.Username, " ", "-", -1)
  686. }
  687. func buildHost() string {
  688. if v := os.Getenv("BUILD_HOST"); v != "" {
  689. return v
  690. }
  691. h, err := os.Hostname()
  692. if err != nil {
  693. return "unknown-host"
  694. }
  695. return h
  696. }
  697. func buildArch() string {
  698. os := goos
  699. if os == "darwin" {
  700. os = "macosx"
  701. }
  702. return fmt.Sprintf("%s-%s", os, goarch)
  703. }
  704. func archiveName(target target) string {
  705. return fmt.Sprintf("%s-%s-%s", target.name, buildArch(), version)
  706. }
  707. func runError(cmd string, args ...string) ([]byte, error) {
  708. if debug {
  709. t0 := time.Now()
  710. log.Println("runError:", cmd, strings.Join(args, " "))
  711. defer func() {
  712. log.Println("... in", time.Since(t0))
  713. }()
  714. }
  715. ecmd := exec.Command(cmd, args...)
  716. bs, err := ecmd.CombinedOutput()
  717. return bytes.TrimSpace(bs), err
  718. }
  719. func runPrint(cmd string, args ...string) {
  720. if debug {
  721. t0 := time.Now()
  722. log.Println("runPrint:", cmd, strings.Join(args, " "))
  723. defer func() {
  724. log.Println("... in", time.Since(t0))
  725. }()
  726. }
  727. ecmd := exec.Command(cmd, args...)
  728. ecmd.Stdout = os.Stdout
  729. ecmd.Stderr = os.Stderr
  730. err := ecmd.Run()
  731. if err != nil {
  732. log.Fatal(err)
  733. }
  734. }
  735. func runPipe(file, cmd string, args ...string) {
  736. if debug {
  737. t0 := time.Now()
  738. log.Println("runPipe:", cmd, strings.Join(args, " "))
  739. defer func() {
  740. log.Println("... in", time.Since(t0))
  741. }()
  742. }
  743. fd, err := os.Create(file)
  744. if err != nil {
  745. log.Fatal(err)
  746. }
  747. ecmd := exec.Command(cmd, args...)
  748. ecmd.Stdout = fd
  749. ecmd.Stderr = os.Stderr
  750. err = ecmd.Run()
  751. if err != nil {
  752. log.Fatal(err)
  753. }
  754. fd.Close()
  755. }
  756. func tarGz(out string, files []archiveFile) {
  757. fd, err := os.Create(out)
  758. if err != nil {
  759. log.Fatal(err)
  760. }
  761. gw := gzip.NewWriter(fd)
  762. tw := tar.NewWriter(gw)
  763. for _, f := range files {
  764. sf, err := os.Open(f.src)
  765. if err != nil {
  766. log.Fatal(err)
  767. }
  768. info, err := sf.Stat()
  769. if err != nil {
  770. log.Fatal(err)
  771. }
  772. h := &tar.Header{
  773. Name: f.dst,
  774. Size: info.Size(),
  775. Mode: int64(info.Mode()),
  776. ModTime: info.ModTime(),
  777. }
  778. err = tw.WriteHeader(h)
  779. if err != nil {
  780. log.Fatal(err)
  781. }
  782. _, err = io.Copy(tw, sf)
  783. if err != nil {
  784. log.Fatal(err)
  785. }
  786. sf.Close()
  787. }
  788. err = tw.Close()
  789. if err != nil {
  790. log.Fatal(err)
  791. }
  792. err = gw.Close()
  793. if err != nil {
  794. log.Fatal(err)
  795. }
  796. err = fd.Close()
  797. if err != nil {
  798. log.Fatal(err)
  799. }
  800. }
  801. func zipFile(out string, files []archiveFile) {
  802. fd, err := os.Create(out)
  803. if err != nil {
  804. log.Fatal(err)
  805. }
  806. zw := zip.NewWriter(fd)
  807. for _, f := range files {
  808. sf, err := os.Open(f.src)
  809. if err != nil {
  810. log.Fatal(err)
  811. }
  812. info, err := sf.Stat()
  813. if err != nil {
  814. log.Fatal(err)
  815. }
  816. fh, err := zip.FileInfoHeader(info)
  817. if err != nil {
  818. log.Fatal(err)
  819. }
  820. fh.Name = filepath.ToSlash(f.dst)
  821. fh.Method = zip.Deflate
  822. if strings.HasSuffix(f.dst, ".txt") {
  823. // Text file. Read it and convert line endings.
  824. bs, err := ioutil.ReadAll(sf)
  825. if err != nil {
  826. log.Fatal(err)
  827. }
  828. bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\n', '\r'}, -1)
  829. fh.UncompressedSize = uint32(len(bs))
  830. fh.UncompressedSize64 = uint64(len(bs))
  831. of, err := zw.CreateHeader(fh)
  832. if err != nil {
  833. log.Fatal(err)
  834. }
  835. of.Write(bs)
  836. } else {
  837. // Binary file. Copy verbatim.
  838. of, err := zw.CreateHeader(fh)
  839. if err != nil {
  840. log.Fatal(err)
  841. }
  842. _, err = io.Copy(of, sf)
  843. if err != nil {
  844. log.Fatal(err)
  845. }
  846. }
  847. }
  848. err = zw.Close()
  849. if err != nil {
  850. log.Fatal(err)
  851. }
  852. err = fd.Close()
  853. if err != nil {
  854. log.Fatal(err)
  855. }
  856. }
  857. func vet(dirs ...string) {
  858. params := []string{"tool", "vet", "-all"}
  859. params = append(params, dirs...)
  860. bs, err := runError("go", params...)
  861. if len(bs) > 0 {
  862. log.Printf("%s", bs)
  863. }
  864. if err != nil {
  865. if exitStatus(err) == 3 {
  866. // Exit code 3, the "vet" tool is not installed
  867. return
  868. }
  869. // A genuine error exit from the vet tool.
  870. log.Fatal(err)
  871. }
  872. }
  873. func lint(pkg string) {
  874. bs, err := runError("golint", pkg)
  875. if err != nil {
  876. log.Println(`- No golint, not linting. Try "go get -u github.com/golang/lint/golint".`)
  877. return
  878. }
  879. analCommentPolicy := regexp.MustCompile(`exported (function|method|const|type|var) [^\s]+ should have comment`)
  880. for _, line := range strings.Split(string(bs), "\n") {
  881. if line == "" {
  882. continue
  883. }
  884. if analCommentPolicy.MatchString(line) {
  885. continue
  886. }
  887. if strings.Contains(line, ".pb.go:") {
  888. continue
  889. }
  890. log.Println(line)
  891. }
  892. }
  893. func macosCodesign(file string) {
  894. if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
  895. bs, err := runError("security", "unlock-keychain", "-p", pass)
  896. if err != nil {
  897. log.Println("Codesign: unlocking keychain failed:", string(bs))
  898. return
  899. }
  900. }
  901. if id := os.Getenv("CODESIGN_IDENTITY"); id != "" {
  902. bs, err := runError("codesign", "-s", id, file)
  903. if err != nil {
  904. log.Println("Codesign: signing failed:", string(bs))
  905. return
  906. }
  907. log.Println("Codesign: successfully signed", file)
  908. }
  909. }
  910. func exitStatus(err error) int {
  911. if err, ok := err.(*exec.ExitError); ok {
  912. if ws, ok := err.ProcessState.Sys().(syscall.WaitStatus); ok {
  913. return ws.ExitStatus()
  914. }
  915. }
  916. return -1
  917. }
  918. func isGometalinterInstalled() bool {
  919. if _, err := runError("gometalinter", "--disable-all"); err != nil {
  920. log.Println("gometalinter is not installed")
  921. return false
  922. }
  923. return true
  924. }
  925. func gometalinter(linter string, dirs []string, excludes ...string) bool {
  926. params := []string{"--disable-all"}
  927. params = append(params, fmt.Sprintf("--deadline=%ds", 60))
  928. params = append(params, "--enable="+linter)
  929. for _, exclude := range excludes {
  930. params = append(params, "--exclude="+exclude)
  931. }
  932. for _, dir := range dirs {
  933. params = append(params, dir)
  934. }
  935. bs, _ := runError("gometalinter", params...)
  936. nerr := 0
  937. for _, line := range strings.Split(string(bs), "\n") {
  938. if line == "" {
  939. continue
  940. }
  941. if strings.Contains(line, ".pb.go:") {
  942. continue
  943. }
  944. log.Println(line)
  945. nerr++
  946. }
  947. return nerr == 0
  948. }