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