github.com/aquanetwork/aquachain@v1.7.8/build/ci.go (about) 1 // Copyright 2016 The aquachain Authors 2 // This file is part of the aquachain library. 3 // 4 // The aquachain library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The aquachain library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the aquachain library. If not, see <http://www.gnu.org/licenses/>. 16 17 // +build none 18 19 package main 20 21 import ( 22 "bufio" 23 "bytes" 24 "encoding/base64" 25 "flag" 26 "fmt" 27 "go/parser" 28 "go/token" 29 "io/ioutil" 30 "log" 31 "os" 32 "os/exec" 33 "path/filepath" 34 "regexp" 35 "runtime" 36 "strings" 37 "time" 38 39 "gitlab.com/aquachain/aquachain/internal/build" 40 ) 41 42 const usage = ` 43 The ci command is called from Continuous Integration scripts. 44 45 Usage: go run build/ci.go <command> <command flags/arguments> 46 47 Available commands are: 48 49 install [ -arch architecture ] [ -cc compiler ] [ -musl ] [-race] [ packages... ] -- builds packages and executables 50 test [ -coverage ] [ packages... ] -- runs the tests 51 lint -- runs certain pre-selected linters 52 archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts 53 importkeys -- imports signing keys from env 54 nsis -- creates a Windows NSIS installer 55 aar [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an Android archive 56 xcode [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an iOS XCode framework 57 xgo [ -alltools ] [ options ] -- cross builds according to options 58 purge [ -store blobstore ] [ -days threshold ] -- purges old archives from the blobstore 59 60 For all commands, -n prevents execution of external programs (dry run mode). 61 62 ` 63 64 var ( 65 // Files that end up in the aquachain*.zip archive. 66 aquaArchiveFiles = []string{ 67 "COPYING", 68 "README.md", 69 executablePath("aquachain"), 70 } 71 72 // Files that end up in the aquachain-alltools*.zip archive. 73 allToolsArchiveFiles = []string{ 74 "COPYING", 75 "README.md", 76 executablePath("aqua-abigen"), 77 executablePath("aqua-maw"), 78 executablePath("aqua-puppet"), 79 executablePath("aqua-rlpdump"), 80 executablePath("aqua-swarm"), 81 executablePath("aqua-vm"), 82 executablePath("aqua-wnode"), 83 executablePath("aquabootnode"), 84 executablePath("aquakey"), 85 executablePath("aquap2psim"), 86 executablePath("aquapaper"), 87 executablePath("aquachain"), 88 executablePath("aquaminer"), 89 } 90 91 // A debian package is created for all executables listed here. 92 debExecutables = []debExecutable{ 93 { 94 Name: "aqua-bootnode", 95 Description: "AquaChain bootnode (discovery-only)", 96 }, 97 { 98 Name: "aqua-vm", 99 Description: "Developer utility version of the AVM (AquaChain Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode", 100 }, 101 { 102 Name: "aqua-rlpdump", 103 Description: "Developer utility tool that prints RLP structures", 104 }, 105 { 106 Name: "aquachain", 107 Description: "AquaChain Full Node and Command Line Wallet", 108 }, 109 } 110 111 // Distros for which packages are created. 112 debDistros = []string{"stretch"} 113 ) 114 115 var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) 116 117 func executablePath(name string) string { 118 if runtime.GOOS == "windows" { 119 name += ".exe" 120 } 121 return filepath.Join(GOBIN, name) 122 } 123 124 func main() { 125 log.SetFlags(log.Lshortfile) 126 127 if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) { 128 log.Fatal(usage + "this script must be run from the root of the repository") 129 } 130 if len(os.Args) < 2 { 131 log.Fatal(usage + "need subcommand as first argument") 132 } 133 switch os.Args[1] { 134 case "install": 135 doInstall(os.Args[2:]) 136 case "test": 137 doTest(os.Args[2:]) 138 case "import-test": 139 doImportTest(os.Args[2:]) 140 case "lint": 141 doLint(os.Args[2:]) 142 case "archive": 143 doArchive(os.Args[2:]) 144 case "debsrc": 145 doDebianSource(os.Args[2:]) 146 case "nsis": 147 doWindowsInstaller(os.Args[2:]) 148 case "aar": 149 doAndroidArchive(os.Args[2:]) 150 case "xcode": 151 doXCodeFramework(os.Args[2:]) 152 case "xgo": 153 doXgo(os.Args[2:]) 154 default: 155 log.Fatal(usage+"unknown command ", os.Args[1]) 156 } 157 } 158 159 // Compiling 160 161 func doInstall(cmdline []string) { 162 var ( 163 arch = flag.String("arch", "", "Architecture to cross build for") 164 cc = flag.String("cc", "", "C compiler to cross build with") 165 ) 166 flag.CommandLine.Parse(cmdline) 167 env := build.Env() 168 // Check Go version. People regularly open issues about compilation 169 // failure with outdated Go. This should save them the trouble. 170 if !strings.Contains(runtime.Version(), "devel") { 171 // Figure out the minor version number since we can't textually compare (1.10 < 1.7) 172 var minor int 173 fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) 174 175 if minor < 10 { 176 log.Println("You have Go version", runtime.Version()) 177 log.Println("aquachain requires at least Go version 1.10 and cannot") 178 log.Println("be compiled with an earlier version. Please upgrade your Go installation.") 179 os.Exit(1) 180 } 181 } 182 // Compile packages given as arguments, or everything if there are no arguments. 183 packages := []string{"./..."} 184 if flag.NArg() > 0 { 185 packages = flag.Args() 186 } 187 packages = build.ExpandPackagesNoVendor(packages) 188 189 if *arch == "" || *arch == runtime.GOARCH { 190 goinstall := goTool("install", buildFlags(env)...) 191 goinstall.Args = append(goinstall.Args, "-v") 192 goinstall.Args = append(goinstall.Args, packages...) 193 build.MustRun(goinstall) 194 return 195 } 196 // If we are cross compiling to ARMv5 ARMv6 or ARMv7, clean any previous builds 197 if *arch == "arm" { 198 os.RemoveAll(filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_arm")) 199 for _, path := range filepath.SplitList(build.GOPATH()) { 200 os.RemoveAll(filepath.Join(path, "pkg", runtime.GOOS+"_arm")) 201 } 202 } 203 // Seems we are cross compiling, work around forbidden GOBIN 204 goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...) 205 goinstall.Args = append(goinstall.Args, "-v") 206 goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...) 207 goinstall.Args = append(goinstall.Args, packages...) 208 build.MustRun(goinstall) 209 210 if cmds, err := ioutil.ReadDir("cmd"); err == nil { 211 for _, cmd := range cmds { 212 pkgs, err := parser.ParseDir(token.NewFileSet(), filepath.Join(".", "cmd", cmd.Name()), nil, parser.PackageClauseOnly) 213 if err != nil { 214 log.Fatal(err) 215 } 216 for name := range pkgs { 217 if name == "main" { 218 gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...) 219 gobuild.Args = append(gobuild.Args, "-v") 220 gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...) 221 gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name())) 222 build.MustRun(gobuild) 223 break 224 } 225 } 226 } 227 } 228 } 229 230 func buildFlags(env build.Environment) (flags []string) { 231 var ld, gc, tags []string 232 if env.Commit != "" { 233 ld = append(ld, "-X", "main.gitCommit="+env.Commit) 234 } 235 236 // make smaller binary 237 ld = append(ld, "-s") 238 ld = append(ld, "-w") 239 240 // use go if possible (net, os/user) 241 tags = append(tags, "netgo", "osusergo") 242 243 // musl-gcc for cgo 244 if env.Config["musl"] { 245 flags = append(flags, "-installsuffix", "musl") 246 os.Setenv("CC", "musl-gcc") 247 } 248 249 // try to produce a static binary 250 if env.Config["static"] { 251 ld = append(ld, "-linkmode external") 252 ld = append(ld, "-extldflags -static") 253 tags = append(tags, "static") 254 } 255 256 // usb support for hardware wallets 257 if env.Config["usb"] { 258 tags = append(tags, "usb") 259 } 260 261 // race detecting binary (SLOW runtime) 262 if env.Config["race"] { 263 flags = append(flags, "-race") 264 } 265 266 if len(tags) > 0 { 267 flags = append(flags, "-tags", strings.Join(tags, " ")) 268 } 269 if len(gc) > 0 { 270 flags = append(flags, "-gcflags", strings.Join(gc, " ")) 271 } 272 flags = append(flags, "-ldflags", strings.Join(ld, " ")) 273 return flags 274 } 275 276 func goTool(subcmd string, args ...string) *exec.Cmd { 277 return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...) 278 } 279 280 func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd { 281 cmd := build.GoTool(subcmd, args...) 282 cmd.Env = []string{"GOPATH=" + build.GOPATH()} 283 if arch == "" || arch == runtime.GOARCH { 284 cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) 285 } else { 286 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 287 cmd.Env = append(cmd.Env, "GOARCH="+arch) 288 } 289 if cc != "" { 290 cmd.Env = append(cmd.Env, "CC="+cc) 291 } 292 for _, e := range os.Environ() { 293 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") { 294 continue 295 } 296 cmd.Env = append(cmd.Env, e) 297 } 298 return cmd 299 } 300 301 // Running The Tests 302 // 303 // "tests" also includes static analysis tools such as vet. 304 305 func doTest(cmdline []string) { 306 var ( 307 coverage = flag.Bool("coverage", false, "Whether to record code coverage") 308 ) 309 flag.CommandLine.Parse(cmdline) 310 env := build.Env() 311 312 packages := []string{"./..."} 313 if len(flag.CommandLine.Args()) > 0 { 314 packages = flag.CommandLine.Args() 315 } 316 packages = build.ExpandPackagesNoVendor(packages) 317 318 // Run analysis tools before the tests. 319 build.MustRun(goTool("vet", packages...)) 320 321 // Run the actual tests. 322 gotest := goTool("test", buildFlags(env)...) 323 // Test a single package at a time. CI builders are slow 324 // and some tests run into timeouts under load. 325 gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "0") 326 if *coverage { 327 gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") 328 } 329 330 gotest.Args = append(gotest.Args, packages...) 331 build.MustRun(gotest) 332 } 333 334 // runs gometalinter on requested packages 335 func doLint(cmdline []string) { 336 flag.CommandLine.Parse(cmdline) 337 338 packages := []string{"./..."} 339 if len(flag.CommandLine.Args()) > 0 { 340 packages = flag.CommandLine.Args() 341 } 342 // Get metalinter and install all supported linters 343 build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v2")) 344 build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), "--install") 345 346 // Run fast linters batched together 347 configs := []string{ 348 "--vendor", 349 "--disable-all", 350 "--enable=vet", 351 "--enable=gofmt", 352 "--enable=misspell", 353 "--enable=goconst", 354 "--min-occurrences=6", // for goconst 355 } 356 build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...) 357 358 // Run slow linters one by one 359 for _, linter := range []string{"unconvert", "gosimple"} { 360 configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter} 361 build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...) 362 } 363 } 364 365 func doImportTest(cmdline []string) { 366 flag.CommandLine.Parse(cmdline) 367 build.Env() 368 bootstrap := "bootstrap.dat" 369 if len(cmdline) == 1 { 370 bootstrap = cmdline[0] 371 } 372 build.MustRunCommand(filepath.Join(GOBIN, "aquachain"), "import", bootstrap) 373 } 374 375 // Release Packaging 376 func doArchive(cmdline []string) { 377 var ( 378 arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") 379 atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") 380 ext string 381 ) 382 flag.CommandLine.Parse(cmdline) 383 switch *atype { 384 case "zip": 385 ext = ".zip" 386 case "tar": 387 ext = ".tar.gz" 388 default: 389 log.Fatal("unknown archive type: ", atype) 390 } 391 392 var ( 393 env = build.Env() 394 base = archiveBasename(*arch, env) 395 aquachain = "aquachain-" + base + ext 396 alltools = "aquachain-alltools-" + base + ext 397 ) 398 maybeSkipArchive(env) 399 if err := build.WriteArchive(aquachain, aquaArchiveFiles); err != nil { 400 log.Fatal(err) 401 } 402 if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil { 403 log.Fatal(err) 404 } 405 } 406 407 func archiveBasename(arch string, env build.Environment) string { 408 platform := runtime.GOOS + "-" + arch 409 if arch == "arm" { 410 platform += os.Getenv("GOARM") 411 } 412 if arch == "android" { 413 platform = "android-all" 414 } 415 if arch == "ios" { 416 platform = "ios-all" 417 } 418 return platform + "-" + archiveVersion(env) 419 } 420 421 func archiveVersion(env build.Environment) string { 422 version := build.VERSION() 423 if isUnstableBuild(env) { 424 version += "-unstable" 425 } 426 if env.Commit != "" { 427 version += "-" + env.Commit[:8] 428 } 429 return version 430 } 431 432 // skips archiving for some build configurations. 433 func maybeSkipArchive(env build.Environment) { 434 if env.IsPullRequest { 435 log.Printf("skipping because this is a PR build") 436 os.Exit(0) 437 } 438 if env.IsCronJob { 439 log.Printf("skipping because this is a cron job") 440 os.Exit(0) 441 } 442 if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { 443 log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) 444 os.Exit(0) 445 } 446 } 447 448 // Debian Packaging 449 450 func doDebianSource(cmdline []string) { 451 var ( 452 signer = flag.String("signer", "", `Signing key name, also used as package author`) 453 upload = flag.String("upload", "", `Where to upload the source package (usually "ppa:aquachain/aquachain")`) 454 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 455 now = time.Now() 456 ) 457 flag.CommandLine.Parse(cmdline) 458 *workdir = makeWorkdir(*workdir) 459 env := build.Env() 460 maybeSkipArchive(env) 461 462 // Import the signing key. 463 if b64key := os.Getenv("PPA_SIGNING_KEY"); b64key != "" { 464 key, err := base64.StdEncoding.DecodeString(b64key) 465 if err != nil { 466 log.Fatal("invalid base64 PPA_SIGNING_KEY") 467 } 468 gpg := exec.Command("gpg", "--import") 469 gpg.Stdin = bytes.NewReader(key) 470 build.MustRun(gpg) 471 } 472 473 // Create the packages. 474 for _, distro := range debDistros { 475 meta := newDebMetadata(distro, *signer, env, now) 476 pkgdir := stageDebianSource(*workdir, meta) 477 debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc") 478 debuild.Dir = pkgdir 479 build.MustRun(debuild) 480 481 changes := fmt.Sprintf("%s_%s_source.changes", meta.Name(), meta.VersionString()) 482 changes = filepath.Join(*workdir, changes) 483 if *signer != "" { 484 build.MustRunCommand("debsign", changes) 485 } 486 if *upload != "" { 487 build.MustRunCommand("dput", *upload, changes) 488 } 489 } 490 } 491 492 func makeWorkdir(wdflag string) string { 493 var err error 494 if wdflag != "" { 495 err = os.MkdirAll(wdflag, 0744) 496 } else { 497 wdflag, err = ioutil.TempDir("", "aquachain-build-") 498 } 499 if err != nil { 500 log.Fatal(err) 501 } 502 return wdflag 503 } 504 505 func isUnstableBuild(env build.Environment) bool { 506 if env.Tag != "" { 507 return false 508 } 509 return true 510 } 511 512 type debMetadata struct { 513 Env build.Environment 514 515 // aquachain version being built. Note that this 516 // is not the debian package version. The package version 517 // is constructed by VersionString. 518 Version string 519 520 Author string // "name <email>", also selects signing key 521 Distro, Time string 522 Executables []debExecutable 523 } 524 525 type debExecutable struct { 526 Name, Description string 527 } 528 529 func newDebMetadata(distro, author string, env build.Environment, t time.Time) debMetadata { 530 if author == "" { 531 // No signing key, use default author. 532 author = "AquaChain Builds <none@example.org>" 533 } 534 return debMetadata{ 535 Env: env, 536 Author: author, 537 Distro: distro, 538 Version: build.VERSION(), 539 Time: t.Format(time.RFC1123Z), 540 Executables: debExecutables, 541 } 542 } 543 544 // Name returns the name of the metapackage that depends 545 // on all executable packages. 546 func (meta debMetadata) Name() string { 547 if isUnstableBuild(meta.Env) { 548 return "aquachain-unstable" 549 } 550 return "aquachain" 551 } 552 553 // VersionString returns the debian version of the packages. 554 func (meta debMetadata) VersionString() string { 555 vsn := meta.Version 556 if meta.Env.Buildnum != "" { 557 vsn += "+build" + meta.Env.Buildnum 558 } 559 if meta.Distro != "" { 560 vsn += "+" + meta.Distro 561 } 562 return vsn 563 } 564 565 // ExeList returns the list of all executable packages. 566 func (meta debMetadata) ExeList() string { 567 names := make([]string, len(meta.Executables)) 568 for i, e := range meta.Executables { 569 names[i] = meta.ExeName(e) 570 } 571 return strings.Join(names, ", ") 572 } 573 574 // ExeName returns the package name of an executable package. 575 func (meta debMetadata) ExeName(exe debExecutable) string { 576 if isUnstableBuild(meta.Env) { 577 return exe.Name + "-unstable" 578 } 579 return exe.Name 580 } 581 582 // ExeConflicts returns the content of the Conflicts field 583 // for executable packages. 584 func (meta debMetadata) ExeConflicts(exe debExecutable) string { 585 if isUnstableBuild(meta.Env) { 586 // Set up the conflicts list so that the *-unstable packages 587 // cannot be installed alongside the regular version. 588 // 589 // https://www.debian.org/doc/debian-policy/ch-relationships.html 590 // is very explicit about Conflicts: and says that Breaks: should 591 // be preferred and the conflicting files should be handled via 592 // alternates. We might do this eventually but using a conflict is 593 // easier now. 594 return "aquachain, " + exe.Name 595 } 596 return "" 597 } 598 599 func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { 600 pkg := meta.Name() + "-" + meta.VersionString() 601 pkgdir = filepath.Join(tmpdir, pkg) 602 if err := os.Mkdir(pkgdir, 0755); err != nil { 603 log.Fatal(err) 604 } 605 606 // Copy the source code. 607 build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator)) 608 609 // Put the debian build files in place. 610 debian := filepath.Join(pkgdir, "debian") 611 build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0755, meta) 612 build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta) 613 build.Render("build/deb.control", filepath.Join(debian, "control"), 0644, meta) 614 build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta) 615 build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta) 616 build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta) 617 for _, exe := range meta.Executables { 618 install := filepath.Join(debian, meta.ExeName(exe)+".install") 619 docs := filepath.Join(debian, meta.ExeName(exe)+".docs") 620 build.Render("build/deb.install", install, 0644, exe) 621 build.Render("build/deb.docs", docs, 0644, exe) 622 } 623 624 return pkgdir 625 } 626 627 // Windows installer 628 629 func doWindowsInstaller(cmdline []string) { 630 // Parse the flags and make skip installer generation on PRs 631 var ( 632 arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") 633 // signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) 634 // upload = flag.String("upload", "", `Destination to upload the archives (usually "aquastore/builds")`) 635 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 636 ) 637 flag.CommandLine.Parse(cmdline) 638 *workdir = makeWorkdir(*workdir) 639 env := build.Env() 640 maybeSkipArchive(env) 641 642 // Aggregate binaries that are included in the installer 643 var ( 644 devTools []string 645 allTools []string 646 aquaTool string 647 ) 648 for _, file := range allToolsArchiveFiles { 649 if file == "COPYING" { // license, copied later 650 continue 651 } 652 allTools = append(allTools, filepath.Base(file)) 653 if filepath.Base(file) == "aquachain.exe" { 654 aquaTool = file 655 } else { 656 devTools = append(devTools, file) 657 } 658 } 659 660 // Render NSIS scripts: Installer NSIS contains two installer sections, 661 // first section contains the aquachain binary, second section holds the dev tools. 662 templateData := map[string]interface{}{ 663 "License": "COPYING", 664 "AquaChain": aquaTool, 665 "DevTools": devTools, 666 } 667 build.Render("build/nsis.aquachain.nsi", filepath.Join(*workdir, "aquachain.nsi"), 0644, nil) 668 build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData) 669 build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools) 670 build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil) 671 build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil) 672 build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0755) 673 build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0755) 674 675 // Build the installer. This assumes that all the needed files have been previously 676 // built (don't mix building and packaging to keep cross compilation complexity to a 677 // minimum). 678 version := strings.Split(build.VERSION(), ".") 679 if env.Commit != "" { 680 version[2] += "-" + env.Commit[:8] 681 } 682 installer, _ := filepath.Abs("aquachain-" + archiveBasename(*arch, env) + ".exe") 683 build.MustRunCommand("makensis.exe", 684 "/DOUTPUTFILE="+installer, 685 "/DMAJORVERSION="+version[0], 686 "/DMINORVERSION="+version[1], 687 "/DBUILDVERSION="+version[2], 688 "/DARCH="+*arch, 689 filepath.Join(*workdir, "aquachain.nsi"), 690 ) 691 } 692 693 // Android archives 694 695 func doAndroidArchive(cmdline []string) { 696 var ( 697 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 698 ) 699 flag.CommandLine.Parse(cmdline) 700 env := build.Env() 701 702 // Sanity check that the SDK and NDK are installed and set 703 if os.Getenv("ANDROID_HOME") == "" { 704 log.Fatal("Please ensure ANDROID_HOME points to your Android SDK") 705 } 706 if os.Getenv("ANDROID_NDK") == "" { 707 log.Fatal("Please ensure ANDROID_NDK points to your Android NDK") 708 } 709 // Build the Android archive and Maven resources 710 build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile")) 711 build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK"))) 712 build.MustRun(gomobileTool("bind", "--target", "android", "--javapkg", "org.aquachain", "-v", "gitlab.com/aquachain/aquachain/opt/mobile")) 713 714 if *local { 715 // If we're building locally, copy bundle to build dir and skip Maven 716 os.Rename("aquachain.aar", filepath.Join(GOBIN, "aquachain.aar")) 717 return 718 } 719 meta := newMavenMetadata(env) 720 build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta) 721 722 // Skip Maven deploy and Azure upload for PR builds 723 maybeSkipArchive(env) 724 725 // Sign and upload the archive to Azure 726 archive := "aquachain-" + archiveBasename("android", env) + ".aar" 727 os.Rename("aquachain.aar", archive) 728 } 729 730 func gomobileTool(subcmd string, args ...string) *exec.Cmd { 731 cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd) 732 cmd.Args = append(cmd.Args, args...) 733 cmd.Env = []string{ 734 "GOPATH=" + build.GOPATH(), 735 } 736 for _, e := range os.Environ() { 737 if strings.HasPrefix(e, "GOPATH=") { 738 continue 739 } 740 cmd.Env = append(cmd.Env, e) 741 } 742 return cmd 743 } 744 745 type mavenMetadata struct { 746 Version string 747 Package string 748 Develop bool 749 Contributors []mavenContributor 750 } 751 752 type mavenContributor struct { 753 Name string 754 Email string 755 } 756 757 func newMavenMetadata(env build.Environment) mavenMetadata { 758 // Collect the list of authors from the repo root 759 contribs := []mavenContributor{} 760 if authors, err := os.Open("AUTHORS"); err == nil { 761 defer authors.Close() 762 763 scanner := bufio.NewScanner(authors) 764 for scanner.Scan() { 765 // Skip any whitespace from the authors list 766 line := strings.TrimSpace(scanner.Text()) 767 if line == "" || line[0] == '#' { 768 continue 769 } 770 // Split the author and insert as a contributor 771 re := regexp.MustCompile("([^<]+) <(.+)>") 772 parts := re.FindStringSubmatch(line) 773 if len(parts) == 3 { 774 contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]}) 775 } 776 } 777 } 778 // Render the version and package strings 779 version := build.VERSION() 780 if isUnstableBuild(env) { 781 version += "-SNAPSHOT" 782 } 783 return mavenMetadata{ 784 Version: version, 785 Package: "aquachain-" + version, 786 Develop: isUnstableBuild(env), 787 Contributors: contribs, 788 } 789 } 790 791 // XCode frameworks 792 793 func doXCodeFramework(cmdline []string) { 794 var ( 795 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 796 // signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) 797 // deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) 798 // upload = flag.String("upload", "", `Destination to upload the archives (usually "aquastore/builds")`) 799 ) 800 flag.CommandLine.Parse(cmdline) 801 env := build.Env() 802 803 // Build the iOS XCode framework 804 build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile")) 805 build.MustRun(gomobileTool("init")) 806 bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "-v", "gitlab.com/aquachain/aquachain/opt/mobile") 807 808 if *local { 809 // If we're building locally, use the build folder and stop afterwards 810 bind.Dir, _ = filepath.Abs(GOBIN) 811 build.MustRun(bind) 812 return 813 } 814 archive := "aquachain-" + archiveBasename("ios", env) 815 if err := os.Mkdir(archive, os.ModePerm); err != nil { 816 log.Fatal(err) 817 } 818 bind.Dir, _ = filepath.Abs(archive) 819 build.MustRun(bind) 820 build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) 821 822 // Skip CocoaPods deploy and Azure upload for PR builds 823 maybeSkipArchive(env) 824 825 } 826 827 type podMetadata struct { 828 Version string 829 Commit string 830 Archive string 831 Contributors []podContributor 832 } 833 834 type podContributor struct { 835 Name string 836 Email string 837 } 838 839 func newPodMetadata(env build.Environment, archive string) podMetadata { 840 // Collect the list of authors from the repo root 841 contribs := []podContributor{} 842 if authors, err := os.Open("AUTHORS"); err == nil { 843 defer authors.Close() 844 845 scanner := bufio.NewScanner(authors) 846 for scanner.Scan() { 847 // Skip any whitespace from the authors list 848 line := strings.TrimSpace(scanner.Text()) 849 if line == "" || line[0] == '#' { 850 continue 851 } 852 // Split the author and insert as a contributor 853 re := regexp.MustCompile("([^<]+) <(.+)>") 854 parts := re.FindStringSubmatch(line) 855 if len(parts) == 3 { 856 contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]}) 857 } 858 } 859 } 860 version := build.VERSION() 861 if isUnstableBuild(env) { 862 version += "-unstable." + env.Buildnum 863 } 864 return podMetadata{ 865 Archive: archive, 866 Version: version, 867 Commit: env.Commit, 868 Contributors: contribs, 869 } 870 } 871 872 // Cross compilation 873 874 func doXgo(cmdline []string) { 875 var ( 876 alltools = flag.Bool("alltools", false, `Flag whether we're building all known tools, or only on in particular`) 877 ) 878 flag.CommandLine.Parse(cmdline) 879 env := build.Env() 880 881 // Make sure xgo is available for cross compilation 882 gogetxgo := goTool("get", "github.com/karalabe/xgo") 883 build.MustRun(gogetxgo) 884 885 // If all tools building is requested, build everything the builder wants 886 args := append(buildFlags(env), flag.Args()...) 887 888 if *alltools { 889 args = append(args, []string{"--dest", GOBIN}...) 890 for _, res := range allToolsArchiveFiles { 891 if strings.HasPrefix(res, GOBIN) { 892 // Binary tool found, cross build it explicitly 893 args = append(args, "./"+filepath.Join("cmd", filepath.Base(res))) 894 xgo := xgoTool(args) 895 build.MustRun(xgo) 896 args = args[:len(args)-1] 897 } 898 } 899 return 900 } 901 // Otherwise execute the explicit cross compilation 902 path := args[len(args)-1] 903 args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...) 904 905 xgo := xgoTool(args) 906 build.MustRun(xgo) 907 } 908 909 func xgoTool(args []string) *exec.Cmd { 910 cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...) 911 cmd.Env = []string{ 912 "GOPATH=" + build.GOPATH(), 913 "GOBIN=" + GOBIN, 914 } 915 for _, e := range os.Environ() { 916 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") { 917 continue 918 } 919 cmd.Env = append(cmd.Env, e) 920 } 921 return cmd 922 }