build.go 28 KB

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