build.go 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433
  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. //go:build tools
  7. // +build tools
  8. package main
  9. import (
  10. "archive/tar"
  11. "archive/zip"
  12. "bytes"
  13. "compress/flate"
  14. "compress/gzip"
  15. "encoding/json"
  16. "errors"
  17. "flag"
  18. "fmt"
  19. "io"
  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. buildpkg "github.com/syncthing/syncthing/lib/build"
  32. "github.com/syncthing/syncthing/lib/upgrade"
  33. "sigs.k8s.io/yaml"
  34. )
  35. var (
  36. goarch string
  37. goos string
  38. noupgrade bool
  39. version string
  40. goCmd string
  41. race bool
  42. debug = os.Getenv("BUILDDEBUG") != ""
  43. extraTags string
  44. installSuffix string
  45. pkgdir string
  46. cc string
  47. run string
  48. benchRun string
  49. buildOut string
  50. debugBinary bool
  51. coverage bool
  52. long bool
  53. timeout = "120s"
  54. longTimeout = "600s"
  55. numVersions = 5
  56. )
  57. type target struct {
  58. name string
  59. debname string
  60. debdeps []string
  61. debpre string
  62. description string
  63. buildPkgs []string
  64. binaryName string
  65. archiveFiles []archiveFile
  66. systemdService string
  67. installationFiles []archiveFile
  68. tags []string
  69. }
  70. type archiveFile struct {
  71. src string
  72. dst string
  73. perm os.FileMode
  74. }
  75. var targets = map[string]target{
  76. "all": {
  77. // Only valid for the "build" and "install" commands as it lacks all
  78. // the archive creation stuff. buildPkgs gets filled out in init()
  79. },
  80. "syncthing": {
  81. // The default target for "build", "install", "tar", "zip", "deb", etc.
  82. name: "syncthing",
  83. debname: "syncthing",
  84. debdeps: []string{"libc6", "procps"},
  85. description: "Open Source Continuous File Synchronization",
  86. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/syncthing"},
  87. binaryName: "syncthing", // .exe will be added automatically for Windows builds
  88. archiveFiles: []archiveFile{
  89. {src: "{{binary}}", dst: "{{binary}}", perm: 0o755},
  90. {src: "README.md", dst: "README.txt", perm: 0o644},
  91. {src: "LICENSE", dst: "LICENSE.txt", perm: 0o644},
  92. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0o644},
  93. // All files from etc/ and extra/ added automatically in init().
  94. },
  95. systemdService: "syncthing@*.service",
  96. installationFiles: []archiveFile{
  97. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0o755},
  98. {src: "README.md", dst: "deb/usr/share/doc/syncthing/README.txt", perm: 0o644},
  99. {src: "LICENSE", dst: "deb/usr/share/doc/syncthing/LICENSE.txt", perm: 0o644},
  100. {src: "AUTHORS", dst: "deb/usr/share/doc/syncthing/AUTHORS.txt", perm: 0o644},
  101. {src: "man/syncthing.1", dst: "deb/usr/share/man/man1/syncthing.1", perm: 0o644},
  102. {src: "man/syncthing-config.5", dst: "deb/usr/share/man/man5/syncthing-config.5", perm: 0o644},
  103. {src: "man/syncthing-stignore.5", dst: "deb/usr/share/man/man5/syncthing-stignore.5", perm: 0o644},
  104. {src: "man/syncthing-device-ids.7", dst: "deb/usr/share/man/man7/syncthing-device-ids.7", perm: 0o644},
  105. {src: "man/syncthing-event-api.7", dst: "deb/usr/share/man/man7/syncthing-event-api.7", perm: 0o644},
  106. {src: "man/syncthing-faq.7", dst: "deb/usr/share/man/man7/syncthing-faq.7", perm: 0o644},
  107. {src: "man/syncthing-networking.7", dst: "deb/usr/share/man/man7/syncthing-networking.7", perm: 0o644},
  108. {src: "man/syncthing-rest-api.7", dst: "deb/usr/share/man/man7/syncthing-rest-api.7", perm: 0o644},
  109. {src: "man/syncthing-security.7", dst: "deb/usr/share/man/man7/syncthing-security.7", perm: 0o644},
  110. {src: "man/syncthing-versioning.7", dst: "deb/usr/share/man/man7/syncthing-versioning.7", perm: 0o644},
  111. {src: "etc/linux-systemd/system/[email protected]", dst: "deb/lib/systemd/system/[email protected]", perm: 0o644},
  112. {src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0o644},
  113. {src: "etc/linux-sysctl/30-syncthing.conf", dst: "deb/usr/lib/sysctl.d/30-syncthing.conf", perm: 0o644},
  114. {src: "etc/firewall-ufw/syncthing", dst: "deb/etc/ufw/applications.d/syncthing", perm: 0o644},
  115. {src: "etc/linux-desktop/syncthing-start.desktop", dst: "deb/usr/share/applications/syncthing-start.desktop", perm: 0o644},
  116. {src: "etc/linux-desktop/syncthing-ui.desktop", dst: "deb/usr/share/applications/syncthing-ui.desktop", perm: 0o644},
  117. {src: "assets/logo-32.png", dst: "deb/usr/share/icons/hicolor/32x32/apps/syncthing.png", perm: 0o644},
  118. {src: "assets/logo-64.png", dst: "deb/usr/share/icons/hicolor/64x64/apps/syncthing.png", perm: 0o644},
  119. {src: "assets/logo-128.png", dst: "deb/usr/share/icons/hicolor/128x128/apps/syncthing.png", perm: 0o644},
  120. {src: "assets/logo-256.png", dst: "deb/usr/share/icons/hicolor/256x256/apps/syncthing.png", perm: 0o644},
  121. {src: "assets/logo-512.png", dst: "deb/usr/share/icons/hicolor/512x512/apps/syncthing.png", perm: 0o644},
  122. {src: "assets/logo-only.svg", dst: "deb/usr/share/icons/hicolor/scalable/apps/syncthing.svg", perm: 0o644},
  123. },
  124. },
  125. "stdiscosrv": {
  126. name: "stdiscosrv",
  127. debname: "syncthing-discosrv",
  128. debdeps: []string{"libc6"},
  129. debpre: "cmd/stdiscosrv/scripts/preinst",
  130. description: "Syncthing Discovery Server",
  131. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/stdiscosrv"},
  132. binaryName: "stdiscosrv", // .exe will be added automatically for Windows builds
  133. archiveFiles: []archiveFile{
  134. {src: "{{binary}}", dst: "{{binary}}", perm: 0o755},
  135. {src: "cmd/stdiscosrv/README.md", dst: "README.txt", perm: 0o644},
  136. {src: "LICENSE", dst: "LICENSE.txt", perm: 0o644},
  137. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0o644},
  138. },
  139. systemdService: "stdiscosrv.service",
  140. installationFiles: []archiveFile{
  141. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0o755},
  142. {src: "cmd/stdiscosrv/README.md", dst: "deb/usr/share/doc/syncthing-discosrv/README.txt", perm: 0o644},
  143. {src: "LICENSE", dst: "deb/usr/share/doc/syncthing-discosrv/LICENSE.txt", perm: 0o644},
  144. {src: "AUTHORS", dst: "deb/usr/share/doc/syncthing-discosrv/AUTHORS.txt", perm: 0o644},
  145. {src: "man/stdiscosrv.1", dst: "deb/usr/share/man/man1/stdiscosrv.1", perm: 0o644},
  146. {src: "cmd/stdiscosrv/etc/linux-systemd/stdiscosrv.service", dst: "deb/lib/systemd/system/stdiscosrv.service", perm: 0o644},
  147. {src: "cmd/stdiscosrv/etc/linux-systemd/default", dst: "deb/etc/default/syncthing-discosrv", perm: 0o644},
  148. {src: "cmd/stdiscosrv/etc/firewall-ufw/stdiscosrv", dst: "deb/etc/ufw/applications.d/stdiscosrv", perm: 0o644},
  149. },
  150. },
  151. "strelaysrv": {
  152. name: "strelaysrv",
  153. debname: "syncthing-relaysrv",
  154. debdeps: []string{"libc6"},
  155. debpre: "cmd/strelaysrv/scripts/preinst",
  156. description: "Syncthing Relay Server",
  157. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/strelaysrv"},
  158. binaryName: "strelaysrv", // .exe will be added automatically for Windows builds
  159. archiveFiles: []archiveFile{
  160. {src: "{{binary}}", dst: "{{binary}}", perm: 0o755},
  161. {src: "cmd/strelaysrv/README.md", dst: "README.txt", perm: 0o644},
  162. {src: "cmd/strelaysrv/LICENSE", dst: "LICENSE.txt", perm: 0o644},
  163. {src: "LICENSE", dst: "LICENSE.txt", perm: 0o644},
  164. {src: "AUTHORS", dst: "AUTHORS.txt", perm: 0o644},
  165. },
  166. systemdService: "strelaysrv.service",
  167. installationFiles: []archiveFile{
  168. {src: "{{binary}}", dst: "deb/usr/bin/{{binary}}", perm: 0o755},
  169. {src: "cmd/strelaysrv/README.md", dst: "deb/usr/share/doc/syncthing-relaysrv/README.txt", perm: 0o644},
  170. {src: "cmd/strelaysrv/LICENSE", dst: "deb/usr/share/doc/syncthing-relaysrv/LICENSE.txt", perm: 0o644},
  171. {src: "LICENSE", dst: "deb/usr/share/doc/syncthing-relaysrv/LICENSE.txt", perm: 0o644},
  172. {src: "AUTHORS", dst: "deb/usr/share/doc/syncthing-relaysrv/AUTHORS.txt", perm: 0o644},
  173. {src: "man/strelaysrv.1", dst: "deb/usr/share/man/man1/strelaysrv.1", perm: 0o644},
  174. {src: "cmd/strelaysrv/etc/linux-systemd/strelaysrv.service", dst: "deb/lib/systemd/system/strelaysrv.service", perm: 0o644},
  175. {src: "cmd/strelaysrv/etc/linux-systemd/default", dst: "deb/etc/default/syncthing-relaysrv", perm: 0o644},
  176. {src: "cmd/strelaysrv/etc/firewall-ufw/strelaysrv", dst: "deb/etc/ufw/applications.d/strelaysrv", perm: 0o644},
  177. },
  178. },
  179. "strelaypoolsrv": {
  180. name: "strelaypoolsrv",
  181. description: "Syncthing Relay Pool Server",
  182. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/infra/strelaypoolsrv"},
  183. binaryName: "strelaypoolsrv",
  184. },
  185. "stupgrades": {
  186. name: "stupgrades",
  187. description: "Syncthing Upgrade Check Server",
  188. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/infra/stupgrades"},
  189. binaryName: "stupgrades",
  190. },
  191. "stcrashreceiver": {
  192. name: "stcrashreceiver",
  193. description: "Syncthing Crash Server",
  194. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/infra/stcrashreceiver"},
  195. binaryName: "stcrashreceiver",
  196. },
  197. "ursrv": {
  198. name: "ursrv",
  199. description: "Syncthing Usage Reporting Server",
  200. buildPkgs: []string{"github.com/syncthing/syncthing/cmd/infra/ursrv"},
  201. binaryName: "ursrv",
  202. },
  203. }
  204. func initTargets() {
  205. all := targets["all"]
  206. pkgs, _ := filepath.Glob("cmd/*")
  207. for _, pkg := range pkgs {
  208. if files, err := filepath.Glob(pkg + "/*.go"); err != nil || len(files) == 0 {
  209. // No go files in the directory
  210. continue
  211. }
  212. all.buildPkgs = append(all.buildPkgs, fmt.Sprintf("github.com/syncthing/syncthing/%s", pkg))
  213. }
  214. targets["all"] = all
  215. // The "syncthing" target includes a few more files found in the "etc"
  216. // and "extra" dirs.
  217. syncthingPkg := targets["syncthing"]
  218. for _, file := range listFiles("etc") {
  219. syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0o644})
  220. }
  221. for _, file := range listFiles("extra") {
  222. syncthingPkg.archiveFiles = append(syncthingPkg.archiveFiles, archiveFile{src: file, dst: file, perm: 0o644})
  223. }
  224. for _, file := range listFiles("extra") {
  225. syncthingPkg.installationFiles = append(syncthingPkg.installationFiles, archiveFile{src: file, dst: "deb/usr/share/doc/syncthing/" + filepath.Base(file), perm: 0o644})
  226. }
  227. targets["syncthing"] = syncthingPkg
  228. }
  229. func main() {
  230. log.SetFlags(0)
  231. parseFlags()
  232. if debug {
  233. t0 := time.Now()
  234. defer func() {
  235. log.Println("... build completed in", time.Since(t0))
  236. }()
  237. }
  238. initTargets()
  239. // Invoking build.go with no parameters at all builds everything (incrementally),
  240. // which is what you want for maximum error checking during development.
  241. if flag.NArg() == 0 {
  242. runCommand("install", targets["all"])
  243. } else {
  244. // with any command given but not a target, the target is
  245. // "syncthing". So "go run build.go install" is "go run build.go install
  246. // syncthing" etc.
  247. targetName := "syncthing"
  248. if flag.NArg() > 1 {
  249. targetName = flag.Arg(1)
  250. }
  251. target, ok := targets[targetName]
  252. if !ok {
  253. log.Fatalln("Unknown target", target)
  254. }
  255. runCommand(flag.Arg(0), target)
  256. }
  257. }
  258. func runCommand(cmd string, target target) {
  259. var tags []string
  260. if noupgrade {
  261. tags = []string{"noupgrade"}
  262. }
  263. tags = append(tags, strings.Fields(extraTags)...)
  264. switch cmd {
  265. case "install":
  266. install(target, tags)
  267. metalintShort()
  268. case "build":
  269. build(target, tags)
  270. case "test":
  271. test(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
  272. case "bench":
  273. bench(strings.Fields(extraTags), "github.com/syncthing/syncthing/internal/...", "github.com/syncthing/syncthing/lib/...", "github.com/syncthing/syncthing/cmd/...")
  274. case "integration":
  275. integration(false)
  276. case "integrationbench":
  277. integration(true)
  278. case "assets":
  279. rebuildAssets()
  280. case "update-deps":
  281. updateDependencies()
  282. case "proto":
  283. proto()
  284. case "testmocks":
  285. testmocks()
  286. case "translate":
  287. translate()
  288. case "transifex":
  289. transifex()
  290. case "weblate":
  291. weblate()
  292. case "tar":
  293. buildTar(target, tags)
  294. writeCompatJSON()
  295. case "zip":
  296. buildZip(target, tags)
  297. writeCompatJSON()
  298. case "deb":
  299. buildDeb(target, tags)
  300. case "vet":
  301. metalintShort()
  302. case "lint":
  303. metalintShort()
  304. case "metalint":
  305. metalint()
  306. case "version":
  307. fmt.Println(getVersion())
  308. case "changelog":
  309. vers, err := currentAndLatestVersions(numVersions)
  310. if err != nil {
  311. log.Fatal(err)
  312. }
  313. for _, ver := range vers {
  314. underline := strings.Repeat("=", len(ver))
  315. msg, err := tagMessage(ver)
  316. if err != nil {
  317. log.Fatal(err)
  318. }
  319. fmt.Printf("%s\n%s\n\n%s\n\n", ver, underline, msg)
  320. }
  321. default:
  322. log.Fatalf("Unknown command %q", cmd)
  323. }
  324. }
  325. func parseFlags() {
  326. flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
  327. flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
  328. flag.StringVar(&goCmd, "gocmd", "go", "Specify `go` command")
  329. flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
  330. flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
  331. flag.BoolVar(&race, "race", race, "Use race detector")
  332. flag.StringVar(&extraTags, "tags", extraTags, "Extra tags, space separated")
  333. flag.StringVar(&installSuffix, "installsuffix", installSuffix, "Install suffix, optional")
  334. flag.StringVar(&pkgdir, "pkgdir", "", "Set -pkgdir parameter for `go build`")
  335. flag.StringVar(&cc, "cc", os.Getenv("CC"), "Set CC environment variable for `go build`")
  336. flag.BoolVar(&debugBinary, "debug-binary", debugBinary, "Create unoptimized binary to use with delve, set -gcflags='-N -l' and omit -ldflags")
  337. flag.BoolVar(&coverage, "coverage", coverage, "Write coverage profile of tests to coverage.txt")
  338. flag.BoolVar(&long, "long", long, "Run tests without the -short flag")
  339. flag.IntVar(&numVersions, "num-versions", numVersions, "Number of versions for changelog command")
  340. flag.StringVar(&run, "run", "", "Specify which tests to run")
  341. flag.StringVar(&benchRun, "bench", "", "Specify which benchmarks to run")
  342. flag.StringVar(&buildOut, "build-out", "", "Set the '-o' value for 'go build'")
  343. flag.Parse()
  344. }
  345. func test(tags []string, pkgs ...string) {
  346. lazyRebuildAssets()
  347. args := []string{"test", "-tags", strings.Join(tags, " ")}
  348. if long {
  349. timeout = longTimeout
  350. } else {
  351. args = append(args, "-short")
  352. }
  353. args = append(args, "-timeout", timeout)
  354. if runtime.GOARCH == "amd64" {
  355. switch runtime.GOOS {
  356. case buildpkg.Darwin, buildpkg.Linux, buildpkg.FreeBSD: // , "windows": # See https://github.com/golang/go/issues/27089
  357. args = append(args, "-race")
  358. }
  359. }
  360. if coverage {
  361. args = append(args, "-covermode", "atomic", "-coverprofile", "coverage.txt", "-coverpkg", strings.Join(pkgs, ","))
  362. }
  363. args = append(args, runArgs()...)
  364. runPrint(goCmd, append(args, pkgs...)...)
  365. }
  366. func bench(tags []string, pkgs ...string) {
  367. lazyRebuildAssets()
  368. args := append([]string{"test", "-run", "NONE", "-tags", strings.Join(tags, " ")}, benchArgs()...)
  369. runPrint(goCmd, append(args, pkgs...)...)
  370. }
  371. func integration(bench bool) {
  372. lazyRebuildAssets()
  373. args := []string{"test", "-v", "-timeout", "60m", "-tags"}
  374. tags := "integration"
  375. if bench {
  376. tags += ",benchmark"
  377. }
  378. args = append(args, tags)
  379. args = append(args, runArgs()...)
  380. if bench {
  381. if run == "" {
  382. args = append(args, "-run", "Benchmark")
  383. }
  384. args = append(args, benchArgs()...)
  385. }
  386. args = append(args, "./test")
  387. fmt.Println(args)
  388. runPrint(goCmd, args...)
  389. }
  390. func runArgs() []string {
  391. if run == "" {
  392. return nil
  393. }
  394. return []string{"-run", run}
  395. }
  396. func benchArgs() []string {
  397. if benchRun == "" {
  398. return []string{"-bench", "."}
  399. }
  400. return []string{"-bench", benchRun}
  401. }
  402. func install(target target, tags []string) {
  403. lazyRebuildAssets()
  404. tags = append(target.tags, tags...)
  405. cwd, err := os.Getwd()
  406. if err != nil {
  407. log.Fatal(err)
  408. }
  409. os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
  410. setBuildEnvVars()
  411. // On Windows generate a special file which the Go compiler will
  412. // automatically use when generating Windows binaries to set things like
  413. // the file icon, version, etc.
  414. if goos == "windows" {
  415. sysoPath, err := shouldBuildSyso(cwd)
  416. if err != nil {
  417. log.Printf("Warning: Windows binaries will not have file information encoded: %v", err)
  418. }
  419. defer shouldCleanupSyso(sysoPath)
  420. }
  421. args := []string{"install"}
  422. args = appendParameters(args, tags, target.buildPkgs...)
  423. runPrint(goCmd, args...)
  424. }
  425. func build(target target, tags []string) {
  426. lazyRebuildAssets()
  427. tags = append(target.tags, tags...)
  428. rmr(target.BinaryName())
  429. setBuildEnvVars()
  430. // On Windows generate a special file which the Go compiler will
  431. // automatically use when generating Windows binaries to set things like
  432. // the file icon, version, etc.
  433. if goos == "windows" {
  434. cwd, err := os.Getwd()
  435. if err != nil {
  436. log.Fatal(err)
  437. }
  438. sysoPath, err := shouldBuildSyso(cwd)
  439. if err != nil {
  440. log.Printf("Warning: Windows binaries will not have file information encoded: %v", err)
  441. }
  442. defer shouldCleanupSyso(sysoPath)
  443. }
  444. args := []string{"build"}
  445. if buildOut != "" {
  446. args = append(args, "-o", buildOut)
  447. }
  448. args = appendParameters(args, tags, target.buildPkgs...)
  449. runPrint(goCmd, args...)
  450. }
  451. func setBuildEnvVars() {
  452. os.Setenv("GOOS", goos)
  453. os.Setenv("GOARCH", goarch)
  454. os.Setenv("CC", cc)
  455. }
  456. func appendParameters(args []string, tags []string, pkgs ...string) []string {
  457. if pkgdir != "" {
  458. args = append(args, "-pkgdir", pkgdir)
  459. }
  460. if len(tags) > 0 {
  461. args = append(args, "-tags", strings.Join(tags, " "))
  462. }
  463. if installSuffix != "" {
  464. args = append(args, "-installsuffix", installSuffix)
  465. }
  466. if race {
  467. args = append(args, "-race")
  468. }
  469. if !debugBinary {
  470. // Regular binaries get version tagged and skip some debug symbols
  471. args = append(args, "-trimpath", "-ldflags", ldflags(tags))
  472. } else {
  473. // -gcflags to disable optimizations and inlining. Skip -ldflags
  474. // because `Could not launch program: decoding dwarf section info at
  475. // offset 0x0: too short` on 'dlv exec ...' see
  476. // https://github.com/go-delve/delve/issues/79
  477. args = append(args, "-gcflags", "all=-N -l")
  478. }
  479. return append(args, pkgs...)
  480. }
  481. func buildTar(target target, tags []string) {
  482. name := archiveName(target)
  483. filename := name + ".tar.gz"
  484. for _, tag := range tags {
  485. if tag == "noupgrade" {
  486. name += "-noupgrade"
  487. break
  488. }
  489. }
  490. build(target, tags)
  491. codesign(target)
  492. for i := range target.archiveFiles {
  493. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.BinaryName(), 1)
  494. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.BinaryName(), 1)
  495. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  496. }
  497. tarGz(filename, target.archiveFiles)
  498. fmt.Println(filename)
  499. }
  500. func buildZip(target target, tags []string) {
  501. name := archiveName(target)
  502. filename := name + ".zip"
  503. for _, tag := range tags {
  504. if tag == "noupgrade" {
  505. name += "-noupgrade"
  506. break
  507. }
  508. }
  509. build(target, tags)
  510. codesign(target)
  511. for i := range target.archiveFiles {
  512. target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.BinaryName(), 1)
  513. target.archiveFiles[i].dst = strings.Replace(target.archiveFiles[i].dst, "{{binary}}", target.BinaryName(), 1)
  514. target.archiveFiles[i].dst = name + "/" + target.archiveFiles[i].dst
  515. }
  516. zipFile(filename, target.archiveFiles)
  517. fmt.Println(filename)
  518. }
  519. func buildDeb(target target, tags []string) {
  520. os.RemoveAll("deb")
  521. // "goarch" here is set to whatever the Debian packages expect. We correct
  522. // it to what we actually know how to build and keep the Debian variant
  523. // name in "debarch".
  524. debarch := goarch
  525. switch goarch {
  526. case "i386":
  527. goarch = "386"
  528. case "armel", "armhf":
  529. goarch = "arm"
  530. }
  531. build(target, append(tags, "noupgrade"))
  532. for i := range target.installationFiles {
  533. target.installationFiles[i].src = strings.Replace(target.installationFiles[i].src, "{{binary}}", target.BinaryName(), 1)
  534. target.installationFiles[i].dst = strings.Replace(target.installationFiles[i].dst, "{{binary}}", target.BinaryName(), 1)
  535. }
  536. for _, af := range target.installationFiles {
  537. if err := copyFile(af.src, af.dst, af.perm); err != nil {
  538. log.Fatal(err)
  539. }
  540. }
  541. maintainer := "Syncthing Release Management <[email protected]>"
  542. debver := version
  543. if strings.HasPrefix(debver, "v") {
  544. debver = debver[1:]
  545. // Debian interprets dashes as separator between main version and
  546. // Debian package version, and thus thinks 0.14.26-rc.1 is better
  547. // than just 0.14.26. This rectifies that.
  548. debver = strings.Replace(debver, "-", "~", -1)
  549. }
  550. if strings.Contains(debver, "_") {
  551. debver = strings.Replace(debver, "_", "~", -1)
  552. }
  553. args := []string{
  554. "-t", "deb",
  555. "-s", "dir",
  556. "-C", "deb",
  557. "-n", target.debname,
  558. "-v", debver,
  559. "-a", debarch,
  560. "-m", maintainer,
  561. "--vendor", maintainer,
  562. "--description", target.description,
  563. "--url", "https://syncthing.net/",
  564. "--license", "MPL-2",
  565. }
  566. for _, dep := range target.debdeps {
  567. args = append(args, "-d", dep)
  568. }
  569. if target.systemdService != "" {
  570. debpost, err := createPostInstScript(target)
  571. defer os.Remove(debpost)
  572. if err != nil {
  573. log.Fatal(err)
  574. }
  575. args = append(args, "--after-upgrade", debpost)
  576. }
  577. if target.debpre != "" {
  578. args = append(args, "--before-install", target.debpre)
  579. }
  580. runPrint("fpm", args...)
  581. }
  582. func createPostInstScript(target target) (string, error) {
  583. scriptname := filepath.Join("script", "deb-post-inst.template")
  584. t, err := template.ParseFiles(scriptname)
  585. if err != nil {
  586. return "", err
  587. }
  588. scriptname = strings.TrimSuffix(scriptname, ".template")
  589. w, err := os.Create(scriptname)
  590. if err != nil {
  591. return "", err
  592. }
  593. defer w.Close()
  594. if err = t.Execute(w, struct {
  595. Service, Command string
  596. }{
  597. target.systemdService, target.binaryName,
  598. }); err != nil {
  599. return "", err
  600. }
  601. return scriptname, nil
  602. }
  603. func shouldBuildSyso(dir string) (string, error) {
  604. type M map[string]interface{}
  605. version := getVersion()
  606. version = strings.TrimPrefix(version, "v")
  607. major, minor, patch := semanticVersion()
  608. bs, err := json.Marshal(M{
  609. "FixedFileInfo": M{
  610. "FileVersion": M{
  611. "Major": major,
  612. "Minor": minor,
  613. "Patch": patch,
  614. },
  615. "ProductVersion": M{
  616. "Major": major,
  617. "Minor": minor,
  618. "Patch": patch,
  619. },
  620. },
  621. "StringFileInfo": M{
  622. "CompanyName": "The Syncthing Authors",
  623. "FileDescription": "Syncthing - Open Source Continuous File Synchronization",
  624. "FileVersion": version,
  625. "InternalName": "syncthing",
  626. "LegalCopyright": "The Syncthing Authors",
  627. "OriginalFilename": "syncthing",
  628. "ProductName": "Syncthing",
  629. "ProductVersion": version,
  630. },
  631. "IconPath": "assets/logo.ico",
  632. })
  633. if err != nil {
  634. return "", err
  635. }
  636. jsonPath := filepath.Join(dir, "versioninfo.json")
  637. err = os.WriteFile(jsonPath, bs, 0o644)
  638. if err != nil {
  639. return "", errors.New("failed to create " + jsonPath + ": " + err.Error())
  640. }
  641. defer func() {
  642. if err := os.Remove(jsonPath); err != nil {
  643. log.Printf("Warning: unable to remove generated %s: %v. Please remove it manually.", jsonPath, err)
  644. }
  645. }()
  646. sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso")
  647. // See https://github.com/josephspurrier/goversioninfo#command-line-flags
  648. arm := strings.HasPrefix(goarch, "arm")
  649. a64 := strings.Contains(goarch, "64")
  650. if _, err := runError("goversioninfo", "-o", sysoPath, fmt.Sprintf("-arm=%v", arm), fmt.Sprintf("-64=%v", a64)); err != nil {
  651. return "", errors.New("failed to create " + sysoPath + ": " + err.Error())
  652. }
  653. return sysoPath, nil
  654. }
  655. func shouldCleanupSyso(sysoFilePath string) {
  656. if sysoFilePath == "" {
  657. return
  658. }
  659. if err := os.Remove(sysoFilePath); err != nil {
  660. log.Printf("Warning: unable to remove generated %s: %v. Please remove it manually.", sysoFilePath, err)
  661. }
  662. }
  663. // copyFile copies a file from src to dst, ensuring the containing directory
  664. // exists. The permission bits are copied as well. If dst already exists and
  665. // the contents are identical to src the modification time is not updated.
  666. func copyFile(src, dst string, perm os.FileMode) error {
  667. in, err := os.ReadFile(src)
  668. if err != nil {
  669. return err
  670. }
  671. out, err := os.ReadFile(dst)
  672. if err != nil {
  673. // The destination probably doesn't exist, we should create
  674. // it.
  675. goto copy
  676. }
  677. if bytes.Equal(in, out) {
  678. // The permission bits may have changed without the contents
  679. // changing so we always mirror them.
  680. os.Chmod(dst, perm)
  681. return nil
  682. }
  683. copy:
  684. os.MkdirAll(filepath.Dir(dst), 0o777)
  685. if err := os.WriteFile(dst, in, perm); err != nil {
  686. return err
  687. }
  688. return nil
  689. }
  690. func listFiles(dir string) []string {
  691. var res []string
  692. filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  693. if err != nil {
  694. return err
  695. }
  696. if fi.Mode().IsRegular() {
  697. res = append(res, path)
  698. }
  699. return nil
  700. })
  701. return res
  702. }
  703. func rebuildAssets() {
  704. os.Setenv("SOURCE_DATE_EPOCH", fmt.Sprint(buildStamp()))
  705. runPrint(goCmd, "generate", "github.com/syncthing/syncthing/lib/api/auto", "github.com/syncthing/syncthing/cmd/infra/strelaypoolsrv/auto")
  706. }
  707. func lazyRebuildAssets() {
  708. shouldRebuild := shouldRebuildAssets("lib/api/auto/gui.files.go", "gui") ||
  709. shouldRebuildAssets("cmd/infra/strelaypoolsrv/auto/gui.files.go", "cmd/infra/strelaypoolsrv/gui")
  710. if shouldRebuild {
  711. rebuildAssets()
  712. }
  713. }
  714. func shouldRebuildAssets(target, srcdir string) bool {
  715. info, err := os.Stat(target)
  716. if err != nil {
  717. // If the file doesn't exist, we must rebuild it
  718. return true
  719. }
  720. // Check if any of the files in gui/ are newer than the asset file. If
  721. // so we should rebuild it.
  722. currentBuild := info.ModTime()
  723. assetsAreNewer := false
  724. stop := errors.New("no need to iterate further")
  725. filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
  726. if err != nil {
  727. return err
  728. }
  729. if info.ModTime().After(currentBuild) {
  730. assetsAreNewer = true
  731. return stop
  732. }
  733. return nil
  734. })
  735. return assetsAreNewer
  736. }
  737. func updateDependencies() {
  738. // Figure out desired Go version
  739. bs, err := os.ReadFile("go.mod")
  740. if err != nil {
  741. log.Fatal(err)
  742. }
  743. re := regexp.MustCompile(`(?m)^go\s+([0-9.]+)`)
  744. matches := re.FindSubmatch(bs)
  745. if len(matches) != 2 {
  746. log.Fatal("failed to parse go.mod")
  747. }
  748. goVersion := string(matches[1])
  749. runPrint(goCmd, "get", "-u", "./...")
  750. runPrint(goCmd, "mod", "tidy", "-go="+goVersion, "-compat="+goVersion)
  751. // We might have updated the protobuf package and should regenerate to match.
  752. proto()
  753. }
  754. func proto() {
  755. // buf needs to be installed
  756. // https://buf.build/docs/installation/
  757. runPrint("buf", "generate")
  758. }
  759. func testmocks() {
  760. args := []string{
  761. "generate",
  762. "github.com/syncthing/syncthing/lib/config",
  763. "github.com/syncthing/syncthing/lib/connections",
  764. "github.com/syncthing/syncthing/lib/discover",
  765. "github.com/syncthing/syncthing/lib/events",
  766. "github.com/syncthing/syncthing/lib/model",
  767. "github.com/syncthing/syncthing/lib/protocol",
  768. }
  769. runPrint(goCmd, args...)
  770. }
  771. func translate() {
  772. os.Chdir("gui/default/assets/lang")
  773. runPipe("lang-en-new.json", goCmd, "run", "../../../../script/translate.go", "lang-en.json", "../../../")
  774. os.Remove("lang-en.json")
  775. err := os.Rename("lang-en-new.json", "lang-en.json")
  776. if err != nil {
  777. log.Fatal(err)
  778. }
  779. os.Chdir("../../../..")
  780. }
  781. func transifex() {
  782. os.Chdir("gui/default/assets/lang")
  783. runPrint(goCmd, "run", "../../../../script/transifexdl.go")
  784. }
  785. func weblate() {
  786. os.Chdir("gui/default/assets/lang")
  787. runPrint(goCmd, "run", "../../../../script/weblatedl.go")
  788. }
  789. func ldflags(tags []string) string {
  790. b := new(strings.Builder)
  791. b.WriteString("-w")
  792. b.WriteString(" -buildid=")
  793. fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Version=%s", version)
  794. fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp=%d", buildStamp())
  795. fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User=%s", buildUser())
  796. fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Host=%s", buildHost())
  797. fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Tags=%s", strings.Join(tags, ","))
  798. if v := os.Getenv("EXTRA_LDFLAGS"); v != "" {
  799. fmt.Fprintf(b, " %s", v)
  800. }
  801. return b.String()
  802. }
  803. func rmr(paths ...string) {
  804. for _, path := range paths {
  805. if debug {
  806. log.Println("rm -r", path)
  807. }
  808. os.RemoveAll(path)
  809. }
  810. }
  811. func getReleaseVersion() (string, error) {
  812. if ver := os.Getenv("VERSION"); ver != "" {
  813. return strings.TrimSpace(ver), nil
  814. }
  815. bs, err := os.ReadFile("RELEASE")
  816. if err != nil {
  817. return "", err
  818. }
  819. return string(bytes.TrimSpace(bs)), nil
  820. }
  821. func getGitVersion() (string, error) {
  822. // The current version as Git sees it
  823. bs, err := runError("git", "describe", "--always", "--dirty", "--abbrev=8")
  824. if err != nil {
  825. return "", err
  826. }
  827. vcur := string(bs)
  828. // The closest current tag name
  829. bs, err = runError("git", "describe", "--always", "--abbrev=0")
  830. if err != nil {
  831. return "", err
  832. }
  833. v0 := string(bs)
  834. // To be more semantic-versionish and ensure proper ordering in our
  835. // upgrade process, we make sure there's only one hyphen in the version.
  836. versionRe := regexp.MustCompile(`-([0-9]{1,3}-g[0-9a-f]{5,10}(-dirty)?)`)
  837. if m := versionRe.FindStringSubmatch(vcur); len(m) > 0 {
  838. suffix := strings.ReplaceAll(m[1], "-", ".")
  839. if strings.Contains(v0, "-") {
  840. // We're based of a tag with a prerelease string. We can just
  841. // add our dev stuff directly.
  842. return fmt.Sprintf("%s.dev.%s", v0, suffix), nil
  843. }
  844. // We're based on a release version. We need to bump the patch
  845. // version and then add a -dev prerelease string.
  846. next := nextPatchVersion(v0)
  847. return fmt.Sprintf("%s-dev.%s", next, suffix), nil
  848. }
  849. return vcur, nil
  850. }
  851. func getVersion() string {
  852. // First try for a RELEASE file or $VERSION env var,
  853. if ver, err := getReleaseVersion(); err == nil {
  854. return ver
  855. }
  856. // ... then see if we have a Git tag.
  857. if ver, err := getGitVersion(); err == nil {
  858. if strings.Contains(ver, "-") {
  859. // The version already contains a hash and stuff. See if we can
  860. // find a current branch name to tack onto it as well.
  861. return ver + getBranchSuffix()
  862. }
  863. return ver
  864. }
  865. // This seems to be a dev build.
  866. return "unknown-dev"
  867. }
  868. func semanticVersion() (major, minor, patch int) {
  869. r := regexp.MustCompile(`v(\d+)\.(\d+).(\d+)`)
  870. matches := r.FindStringSubmatch(getVersion())
  871. if len(matches) != 4 {
  872. return 0, 0, 0
  873. }
  874. var ints [3]int
  875. for i, s := range matches[1:] {
  876. ints[i], _ = strconv.Atoi(s)
  877. }
  878. return ints[0], ints[1], ints[2]
  879. }
  880. func getBranchSuffix() string {
  881. bs, err := runError("git", "branch", "-a", "--contains")
  882. if err != nil {
  883. return ""
  884. }
  885. branches := strings.Split(string(bs), "\n")
  886. if len(branches) == 0 {
  887. return ""
  888. }
  889. branch := ""
  890. for i, candidate := range branches {
  891. if strings.HasPrefix(candidate, "*") {
  892. // This is the current branch. Select it!
  893. branch = strings.TrimLeft(candidate, " \t*")
  894. break
  895. } else if i == 0 {
  896. // Otherwise the first branch in the list will do.
  897. branch = strings.TrimSpace(branch)
  898. }
  899. }
  900. if branch == "" {
  901. return ""
  902. }
  903. // The branch name may be on the form "remotes/origin/foo" from which we
  904. // just want "foo".
  905. parts := strings.Split(branch, "/")
  906. if len(parts) == 0 || len(parts[len(parts)-1]) == 0 {
  907. return ""
  908. }
  909. branch = parts[len(parts)-1]
  910. switch branch {
  911. case "release", "main":
  912. // these are not special
  913. return ""
  914. }
  915. if strings.HasPrefix(branch, "release-") {
  916. // release branches are not special
  917. return ""
  918. }
  919. validBranchRe := regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
  920. if !validBranchRe.MatchString(branch) {
  921. // There's some odd stuff in the branch name. Better skip it.
  922. return ""
  923. }
  924. return "-" + branch
  925. }
  926. func buildStamp() int64 {
  927. // If SOURCE_DATE_EPOCH is set, use that.
  928. if s, _ := strconv.ParseInt(os.Getenv("SOURCE_DATE_EPOCH"), 10, 64); s > 0 {
  929. return s
  930. }
  931. // Try to get the timestamp of the latest commit.
  932. bs, err := runError("git", "show", "-s", "--format=%ct")
  933. if err != nil {
  934. // Fall back to "now".
  935. return time.Now().Unix()
  936. }
  937. s, _ := strconv.ParseInt(string(bs), 10, 64)
  938. return s
  939. }
  940. func buildUser() string {
  941. if v := os.Getenv("BUILD_USER"); v != "" {
  942. return v
  943. }
  944. u, err := user.Current()
  945. if err != nil {
  946. return "unknown-user"
  947. }
  948. return strings.Replace(u.Username, " ", "-", -1)
  949. }
  950. func buildHost() string {
  951. if v := os.Getenv("BUILD_HOST"); v != "" {
  952. return v
  953. }
  954. h, err := os.Hostname()
  955. if err != nil {
  956. return "unknown-host"
  957. }
  958. return h
  959. }
  960. func buildArch() string {
  961. os := goos
  962. if os == "darwin" {
  963. os = "macos"
  964. }
  965. return fmt.Sprintf("%s-%s", os, goarch)
  966. }
  967. func archiveName(target target) string {
  968. return fmt.Sprintf("%s-%s-%s", target.name, buildArch(), version)
  969. }
  970. func runError(cmd string, args ...string) ([]byte, error) {
  971. if debug {
  972. t0 := time.Now()
  973. log.Println("runError:", cmd, strings.Join(args, " "))
  974. defer func() {
  975. log.Println("... in", time.Since(t0))
  976. }()
  977. }
  978. ecmd := exec.Command(cmd, args...)
  979. bs, err := ecmd.CombinedOutput()
  980. return bytes.TrimSpace(bs), err
  981. }
  982. func runPrint(cmd string, args ...string) {
  983. runPrintInDir(".", cmd, args...)
  984. }
  985. func runPrintInDir(dir string, cmd string, args ...string) {
  986. if debug {
  987. t0 := time.Now()
  988. log.Println("runPrint:", cmd, strings.Join(args, " "))
  989. defer func() {
  990. log.Println("... in", time.Since(t0))
  991. }()
  992. }
  993. ecmd := exec.Command(cmd, args...)
  994. ecmd.Stdout = os.Stdout
  995. ecmd.Stderr = os.Stderr
  996. ecmd.Dir = dir
  997. err := ecmd.Run()
  998. if err != nil {
  999. log.Fatal(err)
  1000. }
  1001. }
  1002. func runPipe(file, cmd string, args ...string) {
  1003. if debug {
  1004. t0 := time.Now()
  1005. log.Println("runPipe:", cmd, strings.Join(args, " "))
  1006. defer func() {
  1007. log.Println("... in", time.Since(t0))
  1008. }()
  1009. }
  1010. fd, err := os.Create(file)
  1011. if err != nil {
  1012. log.Fatal(err)
  1013. }
  1014. ecmd := exec.Command(cmd, args...)
  1015. ecmd.Stdout = fd
  1016. ecmd.Stderr = os.Stderr
  1017. err = ecmd.Run()
  1018. if err != nil {
  1019. log.Fatal(err)
  1020. }
  1021. fd.Close()
  1022. }
  1023. func tarGz(out string, files []archiveFile) {
  1024. fd, err := os.Create(out)
  1025. if err != nil {
  1026. log.Fatal(err)
  1027. }
  1028. gw, err := gzip.NewWriterLevel(fd, gzip.BestCompression)
  1029. if err != nil {
  1030. log.Fatal(err)
  1031. }
  1032. tw := tar.NewWriter(gw)
  1033. for _, f := range files {
  1034. sf, err := os.Open(f.src)
  1035. if err != nil {
  1036. log.Fatal(err)
  1037. }
  1038. info, err := sf.Stat()
  1039. if err != nil {
  1040. log.Fatal(err)
  1041. }
  1042. h := &tar.Header{
  1043. Name: f.dst,
  1044. Size: info.Size(),
  1045. Mode: int64(info.Mode()),
  1046. ModTime: info.ModTime(),
  1047. }
  1048. err = tw.WriteHeader(h)
  1049. if err != nil {
  1050. log.Fatal(err)
  1051. }
  1052. _, err = io.Copy(tw, sf)
  1053. if err != nil {
  1054. log.Fatal(err)
  1055. }
  1056. sf.Close()
  1057. }
  1058. err = tw.Close()
  1059. if err != nil {
  1060. log.Fatal(err)
  1061. }
  1062. err = gw.Close()
  1063. if err != nil {
  1064. log.Fatal(err)
  1065. }
  1066. err = fd.Close()
  1067. if err != nil {
  1068. log.Fatal(err)
  1069. }
  1070. }
  1071. func zipFile(out string, files []archiveFile) {
  1072. fd, err := os.Create(out)
  1073. if err != nil {
  1074. log.Fatal(err)
  1075. }
  1076. zw := zip.NewWriter(fd)
  1077. var fw *flate.Writer
  1078. // Register the deflator.
  1079. zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
  1080. var err error
  1081. if fw == nil {
  1082. // Creating a flate compressor for every file is
  1083. // expensive, create one and reuse it.
  1084. fw, err = flate.NewWriter(out, flate.BestCompression)
  1085. } else {
  1086. fw.Reset(out)
  1087. }
  1088. return fw, err
  1089. })
  1090. for _, f := range files {
  1091. sf, err := os.Open(f.src)
  1092. if err != nil {
  1093. log.Fatal(err)
  1094. }
  1095. info, err := sf.Stat()
  1096. if err != nil {
  1097. log.Fatal(err)
  1098. }
  1099. fh, err := zip.FileInfoHeader(info)
  1100. if err != nil {
  1101. log.Fatal(err)
  1102. }
  1103. fh.Name = filepath.ToSlash(f.dst)
  1104. fh.Method = zip.Deflate
  1105. if strings.HasSuffix(f.dst, ".txt") {
  1106. // Text file. Read it and convert line endings.
  1107. bs, err := io.ReadAll(sf)
  1108. if err != nil {
  1109. log.Fatal(err)
  1110. }
  1111. bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\r', '\n'}, -1)
  1112. fh.UncompressedSize = uint32(len(bs))
  1113. fh.UncompressedSize64 = uint64(len(bs))
  1114. of, err := zw.CreateHeader(fh)
  1115. if err != nil {
  1116. log.Fatal(err)
  1117. }
  1118. of.Write(bs)
  1119. } else {
  1120. // Binary file. Copy verbatim.
  1121. of, err := zw.CreateHeader(fh)
  1122. if err != nil {
  1123. log.Fatal(err)
  1124. }
  1125. _, err = io.Copy(of, sf)
  1126. if err != nil {
  1127. log.Fatal(err)
  1128. }
  1129. }
  1130. }
  1131. err = zw.Close()
  1132. if err != nil {
  1133. log.Fatal(err)
  1134. }
  1135. err = fd.Close()
  1136. if err != nil {
  1137. log.Fatal(err)
  1138. }
  1139. }
  1140. func codesign(target target) {
  1141. if goos == "darwin" {
  1142. macosCodesign(target.BinaryName())
  1143. }
  1144. }
  1145. func macosCodesign(file string) {
  1146. if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
  1147. bs, err := runError("security", "unlock-keychain", "-p", pass)
  1148. if err != nil {
  1149. log.Println("Codesign: unlocking keychain failed:", string(bs))
  1150. return
  1151. }
  1152. }
  1153. if id := os.Getenv("CODESIGN_IDENTITY"); id != "" {
  1154. bs, err := runError("codesign", "--options=runtime", "-s", id, file)
  1155. if err != nil {
  1156. log.Println("Codesign: signing failed:", string(bs))
  1157. return
  1158. }
  1159. log.Println("Codesign: successfully signed", file)
  1160. }
  1161. }
  1162. func metalint() {
  1163. lazyRebuildAssets()
  1164. runPrint(goCmd, "test", "-run", "Metalint", "./meta")
  1165. }
  1166. func metalintShort() {
  1167. lazyRebuildAssets()
  1168. runPrint(goCmd, "test", "-short", "-run", "Metalint", "./meta")
  1169. }
  1170. func (t target) BinaryName() string {
  1171. if goos == "windows" {
  1172. return t.binaryName + ".exe"
  1173. }
  1174. return t.binaryName
  1175. }
  1176. func currentAndLatestVersions(n int) ([]string, error) {
  1177. bs, err := runError("git", "tag", "--sort", "taggerdate")
  1178. if err != nil {
  1179. return nil, err
  1180. }
  1181. lines := strings.Split(string(bs), "\n")
  1182. reverseStrings(lines)
  1183. // The one at the head is the latest version. We always keep that one.
  1184. // Then we filter out remaining ones with dashes (pre-releases etc).
  1185. latest := lines[:1]
  1186. nonPres := filterStrings(lines[1:], func(s string) bool { return !strings.Contains(s, "-") })
  1187. vers := append(latest, nonPres...)
  1188. return vers[:n], nil
  1189. }
  1190. func reverseStrings(ss []string) {
  1191. for i := 0; i < len(ss)/2; i++ {
  1192. ss[i], ss[len(ss)-1-i] = ss[len(ss)-1-i], ss[i]
  1193. }
  1194. }
  1195. func filterStrings(ss []string, op func(string) bool) []string {
  1196. n := ss[:0]
  1197. for _, s := range ss {
  1198. if op(s) {
  1199. n = append(n, s)
  1200. }
  1201. }
  1202. return n
  1203. }
  1204. func tagMessage(tag string) (string, error) {
  1205. hash, err := runError("git", "rev-parse", tag)
  1206. if err != nil {
  1207. return "", err
  1208. }
  1209. obj, err := runError("git", "cat-file", "-p", string(hash))
  1210. if err != nil {
  1211. return "", err
  1212. }
  1213. return trimTagMessage(string(obj), tag), nil
  1214. }
  1215. func trimTagMessage(msg, tag string) string {
  1216. firstBlank := strings.Index(msg, "\n\n")
  1217. if firstBlank > 0 {
  1218. msg = msg[firstBlank+2:]
  1219. }
  1220. msg = strings.TrimPrefix(msg, tag)
  1221. beginSig := strings.Index(msg, "-----BEGIN PGP")
  1222. if beginSig > 0 {
  1223. msg = msg[:beginSig]
  1224. }
  1225. return strings.TrimSpace(msg)
  1226. }
  1227. func nextPatchVersion(ver string) string {
  1228. parts := strings.SplitN(ver, "-", 2)
  1229. digits := strings.Split(parts[0], ".")
  1230. n, _ := strconv.Atoi(digits[len(digits)-1])
  1231. digits[len(digits)-1] = strconv.Itoa(n + 1)
  1232. return strings.Join(digits, ".")
  1233. }
  1234. func writeCompatJSON() {
  1235. bs, err := os.ReadFile("compat.yaml")
  1236. if err != nil {
  1237. log.Fatal("Reading compat.yaml:", err)
  1238. }
  1239. var entries []upgrade.ReleaseCompatibility
  1240. if err := yaml.Unmarshal(bs, &entries); err != nil {
  1241. log.Fatal("Parsing compat.yaml:", err)
  1242. }
  1243. rt := runtime.Version()
  1244. for _, e := range entries {
  1245. if !strings.HasPrefix(rt, e.Runtime) {
  1246. continue
  1247. }
  1248. bs, _ := json.MarshalIndent(e, "", " ")
  1249. if err := os.WriteFile("compat.json", bs, 0o644); err != nil {
  1250. log.Fatal("Writing compat.json:", err)
  1251. }
  1252. return
  1253. }
  1254. log.Fatalf("runtime %v not found in compat.yaml", rt)
  1255. }