build.go 25 KB

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