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  }