build.go 30 KB

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