build.go 43 KB

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