build.go 33 KB

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