github.com/elastos/Elastos.ELA.SideChain.ETH@v0.2.2/build/ci.go (about)

     1  // Copyright 2016 The Elastos.ELA.SideChain.ESC Authors
     2  // This file is part of the Elastos.ELA.SideChain.ESC library.
     3  //
     4  // The Elastos.ELA.SideChain.ESC 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 Elastos.ELA.SideChain.ESC 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 Elastos.ELA.SideChain.ESC library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // +build none
    18  
    19  /*
    20  The ci command is called from Continuous Integration scripts.
    21  
    22  Usage: go run build/ci.go <command> <command flags/arguments>
    23  
    24  Available commands are:
    25  
    26     install    [ -arch architecture ] [ -cc compiler ] [ packages... ]                          -- builds packages and executables
    27     test       [ -coverage ] [ packages... ]                                                    -- runs the tests
    28     lint                                                                                        -- runs certain pre-selected linters
    29     archive    [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts
    30     importkeys                                                                                  -- imports signing keys from env
    31     debsrc     [ -signer key-id ] [ -upload dest ]                                              -- creates a debian source package
    32     nsis                                                                                        -- creates a Windows NSIS installer
    33     aar        [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ]                      -- creates an Android archive
    34     xcode      [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ]                      -- creates an iOS XCode framework
    35     xgo        [ -alltools ] [ options ]                                                        -- cross builds according to options
    36     purge      [ -store blobstore ] [ -days threshold ]                                         -- purges old archives from the blobstore
    37  
    38  For all commands, -n prevents execution of external programs (dry run mode).
    39  
    40  */
    41  package main
    42  
    43  import (
    44  	"bufio"
    45  	"bytes"
    46  	"encoding/base64"
    47  	"flag"
    48  	"fmt"
    49  	"go/parser"
    50  	"go/token"
    51  	"io/ioutil"
    52  	"log"
    53  	"os"
    54  	"os/exec"
    55  	"path/filepath"
    56  	"regexp"
    57  	"runtime"
    58  	"strings"
    59  	"time"
    60  
    61  	"github.com/elastos/Elastos.ELA.SideChain.ESC/common/hexutil"
    62  	"github.com/elastos/Elastos.ELA.SideChain.ESC/internal/build"
    63  	"github.com/elastos/Elastos.ELA.SideChain.ESC/params"
    64  )
    65  
    66  var (
    67  	// Files that end up in the geth*.zip archive.
    68  	gethArchiveFiles = []string{
    69  		"COPYING",
    70  		executablePath("geth"),
    71  	}
    72  
    73  	// Files that end up in the geth-alltools*.zip archive.
    74  	allToolsArchiveFiles = []string{
    75  		"COPYING",
    76  		executablePath("abigen"),
    77  		executablePath("bootnode"),
    78  		executablePath("evm"),
    79  		executablePath("geth"),
    80  		executablePath("puppeth"),
    81  		executablePath("rlpdump"),
    82  		executablePath("wnode"),
    83  		executablePath("clef"),
    84  	}
    85  
    86  	// A debian package is created for all executables listed here.
    87  	debExecutables = []debExecutable{
    88  		{
    89  			BinaryName:  "abigen",
    90  			Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
    91  		},
    92  		{
    93  			BinaryName:  "bootnode",
    94  			Description: "Ethereum bootnode.",
    95  		},
    96  		{
    97  			BinaryName:  "evm",
    98  			Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
    99  		},
   100  		{
   101  			BinaryName:  "geth",
   102  			Description: "Ethereum CLI client.",
   103  		},
   104  		{
   105  			BinaryName:  "puppeth",
   106  			Description: "Ethereum private network manager.",
   107  		},
   108  		{
   109  			BinaryName:  "rlpdump",
   110  			Description: "Developer utility tool that prints RLP structures.",
   111  		},
   112  		{
   113  			BinaryName:  "wnode",
   114  			Description: "Ethereum Whisper diagnostic tool",
   115  		},
   116  		{
   117  			BinaryName:  "clef",
   118  			Description: "Ethereum account management tool.",
   119  		},
   120  	}
   121  
   122  	// A debian package is created for all executables listed here.
   123  
   124  	debEthereum = debPackage{
   125  		Name:        "ethereum",
   126  		Version:     params.Version,
   127  		Executables: debExecutables,
   128  	}
   129  
   130  	// Debian meta packages to build and push to Ubuntu PPA
   131  	debPackages = []debPackage{
   132  		debEthereum,
   133  	}
   134  
   135  	// Distros for which packages are created.
   136  	// Note: vivid is unsupported because there is no golang-1.6 package for it.
   137  	// Note: wily is unsupported because it was officially deprecated on Launchpad.
   138  	// Note: yakkety is unsupported because it was officially deprecated on Launchpad.
   139  	// Note: zesty is unsupported because it was officially deprecated on Launchpad.
   140  	// Note: artful is unsupported because it was officially deprecated on Launchpad.
   141  	// Note: cosmic is unsupported because it was officially deprecated on Launchpad.
   142  	debDistroGoBoots = map[string]string{
   143  		"trusty": "golang-1.11",
   144  		"xenial": "golang-go",
   145  		"bionic": "golang-go",
   146  		"disco":  "golang-go",
   147  		"eoan":   "golang-go",
   148  	}
   149  
   150  	debGoBootPaths = map[string]string{
   151  		"golang-1.11": "/usr/lib/go-1.11",
   152  		"golang-go":   "/usr/lib/go",
   153  	}
   154  )
   155  
   156  var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
   157  
   158  func executablePath(name string) string {
   159  	if runtime.GOOS == "windows" {
   160  		name += ".exe"
   161  	}
   162  	return filepath.Join(GOBIN, name)
   163  }
   164  
   165  func main() {
   166  	log.SetFlags(log.Lshortfile)
   167  
   168  	if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) {
   169  		log.Fatal("this script must be run from the root of the repository")
   170  	}
   171  	if len(os.Args) < 2 {
   172  		log.Fatal("need subcommand as first argument")
   173  	}
   174  	switch os.Args[1] {
   175  	case "install":
   176  		doInstall(os.Args[2:])
   177  	case "test":
   178  		doTest(os.Args[2:])
   179  	case "lint":
   180  		doLint(os.Args[2:])
   181  	case "archive":
   182  		doArchive(os.Args[2:])
   183  	case "debsrc":
   184  		doDebianSource(os.Args[2:])
   185  	case "nsis":
   186  		doWindowsInstaller(os.Args[2:])
   187  	case "aar":
   188  		doAndroidArchive(os.Args[2:])
   189  	case "xcode":
   190  		doXCodeFramework(os.Args[2:])
   191  	case "xgo":
   192  		doXgo(os.Args[2:])
   193  	case "purge":
   194  		doPurge(os.Args[2:])
   195  	default:
   196  		log.Fatal("unknown command ", os.Args[1])
   197  	}
   198  }
   199  
   200  // Compiling
   201  
   202  func doInstall(cmdline []string) {
   203  	var (
   204  		arch = flag.String("arch", "", "Architecture to cross build for")
   205  		cc   = flag.String("cc", "", "C compiler to cross build with")
   206  	)
   207  	flag.CommandLine.Parse(cmdline)
   208  	env := build.Env()
   209  
   210  	// Check Go version. People regularly open issues about compilation
   211  	// failure with outdated Go. This should save them the trouble.
   212  	if !strings.Contains(runtime.Version(), "devel") {
   213  		// Figure out the minor version number since we can't textually compare (1.10 < 1.9)
   214  		var minor int
   215  		fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
   216  
   217  		if minor < 9 {
   218  			log.Println("You have Go version", runtime.Version())
   219  			log.Println("Elastos.ELA.SideChain.ESC requires at least Go version 1.9 and cannot")
   220  			log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
   221  			os.Exit(1)
   222  		}
   223  	}
   224  	// Compile packages given as arguments, or everything if there are no arguments.
   225  	packages := []string{"./..."}
   226  	if flag.NArg() > 0 {
   227  		packages = flag.Args()
   228  	}
   229  
   230  	if *arch == "" || *arch == runtime.GOARCH {
   231  		goinstall := goTool("install", buildFlags(env)...)
   232  		goinstall.Args = append(goinstall.Args, "-v")
   233  		goinstall.Args = append(goinstall.Args, packages...)
   234  		build.MustRun(goinstall)
   235  		return
   236  	}
   237  	// If we are cross compiling to ARMv5 ARMv6 or ARMv7, clean any previous builds
   238  	if *arch == "arm" {
   239  		os.RemoveAll(filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_arm"))
   240  		for _, path := range filepath.SplitList(build.GOPATH()) {
   241  			os.RemoveAll(filepath.Join(path, "pkg", runtime.GOOS+"_arm"))
   242  		}
   243  	}
   244  	// Seems we are cross compiling, work around forbidden GOBIN
   245  	goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...)
   246  	goinstall.Args = append(goinstall.Args, "-v")
   247  	goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...)
   248  	goinstall.Args = append(goinstall.Args, packages...)
   249  	build.MustRun(goinstall)
   250  
   251  	if cmds, err := ioutil.ReadDir("cmd"); err == nil {
   252  		for _, cmd := range cmds {
   253  			pkgs, err := parser.ParseDir(token.NewFileSet(), filepath.Join(".", "cmd", cmd.Name()), nil, parser.PackageClauseOnly)
   254  			if err != nil {
   255  				log.Fatal(err)
   256  			}
   257  			for name := range pkgs {
   258  				if name == "main" {
   259  					gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...)
   260  					gobuild.Args = append(gobuild.Args, "-v")
   261  					gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...)
   262  					gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name()))
   263  					build.MustRun(gobuild)
   264  					break
   265  				}
   266  			}
   267  		}
   268  	}
   269  }
   270  
   271  func buildFlags(env build.Environment) (flags []string) {
   272  	var ld []string
   273  	if env.Commit != "" {
   274  		ld = append(ld, "-X", "main.gitCommit="+env.Commit)
   275  		ld = append(ld, "-X", "main.gitDate="+env.Date)
   276  	}
   277  	if runtime.GOOS == "darwin" {
   278  		ld = append(ld, "-s")
   279  	}
   280  
   281  	if len(ld) > 0 {
   282  		flags = append(flags, "-ldflags", strings.Join(ld, " "))
   283  	}
   284  	return flags
   285  }
   286  
   287  func goTool(subcmd string, args ...string) *exec.Cmd {
   288  	return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...)
   289  }
   290  
   291  func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd {
   292  	cmd := build.GoTool(subcmd, args...)
   293  	cmd.Env = []string{"GOPATH=" + build.GOPATH()}
   294  	if arch == "" || arch == runtime.GOARCH {
   295  		cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
   296  	} else {
   297  		cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
   298  		cmd.Env = append(cmd.Env, "GOARCH="+arch)
   299  	}
   300  	if cc != "" {
   301  		cmd.Env = append(cmd.Env, "CC="+cc)
   302  	}
   303  	for _, e := range os.Environ() {
   304  		if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
   305  			continue
   306  		}
   307  		cmd.Env = append(cmd.Env, e)
   308  	}
   309  	return cmd
   310  }
   311  
   312  // Running The Tests
   313  //
   314  // "tests" also includes static analysis tools such as vet.
   315  
   316  func doTest(cmdline []string) {
   317  	coverage := flag.Bool("coverage", false, "Whether to record code coverage")
   318  	flag.CommandLine.Parse(cmdline)
   319  	env := build.Env()
   320  
   321  	packages := []string{"./..."}
   322  	if len(flag.CommandLine.Args()) > 0 {
   323  		packages = flag.CommandLine.Args()
   324  	}
   325  
   326  	// Run the actual tests.
   327  	// Test a single package at a time. CI builders are slow
   328  	// and some tests run into timeouts under load.
   329  	gotest := goTool("test", buildFlags(env)...)
   330  	gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "5m", "--short")
   331  	if *coverage {
   332  		gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
   333  	}
   334  
   335  	gotest.Args = append(gotest.Args, packages...)
   336  	build.MustRun(gotest)
   337  }
   338  
   339  // runs gometalinter on requested packages
   340  func doLint(cmdline []string) {
   341  	return
   342  	flag.CommandLine.Parse(cmdline)
   343  
   344  	packages := []string{"./..."}
   345  	if len(flag.CommandLine.Args()) > 0 {
   346  		packages = flag.CommandLine.Args()
   347  	}
   348  	// Get metalinter and install all supported linters
   349  	build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v2"))
   350  	build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), "--install")
   351  
   352  	// Run fast linters batched together
   353  	configs := []string{
   354  		"--vendor",
   355  		"--tests",
   356  		"--deadline=2m",
   357  		"--disable-all",
   358  		"--enable=goimports",
   359  		"--enable=varcheck",
   360  		"--enable=vet",
   361  		"--enable=gofmt",
   362  		"--enable=misspell",
   363  		"--enable=goconst",
   364  		"--min-occurrences=6", // for goconst
   365  	}
   366  	build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
   367  
   368  	// Run slow linters one by one
   369  	for _, linter := range []string{"unconvert", "gosimple"} {
   370  		configs = []string{"--vendor", "--tests", "--deadline=10m", "--disable-all", "--enable=" + linter}
   371  		build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
   372  	}
   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  		signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`)
   381  		upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
   382  		ext    string
   383  	)
   384  	flag.CommandLine.Parse(cmdline)
   385  	switch *atype {
   386  	case "zip":
   387  		ext = ".zip"
   388  	case "tar":
   389  		ext = ".tar.gz"
   390  	default:
   391  		log.Fatal("unknown archive type: ", atype)
   392  	}
   393  
   394  	var (
   395  		env = build.Env()
   396  
   397  		basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit))
   398  		geth     = "geth-" + basegeth + ext
   399  		alltools = "geth-alltools-" + basegeth + ext
   400  	)
   401  	maybeSkipArchive(env)
   402  	if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
   403  		log.Fatal(err)
   404  	}
   405  	if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
   406  		log.Fatal(err)
   407  	}
   408  	for _, archive := range []string{geth, alltools} {
   409  		if err := archiveUpload(archive, *upload, *signer); err != nil {
   410  			log.Fatal(err)
   411  		}
   412  	}
   413  }
   414  
   415  func archiveBasename(arch string, archiveVersion string) string {
   416  	platform := runtime.GOOS + "-" + arch
   417  	if arch == "arm" {
   418  		platform += os.Getenv("GOARM")
   419  	}
   420  	if arch == "android" {
   421  		platform = "android-all"
   422  	}
   423  	if arch == "ios" {
   424  		platform = "ios-all"
   425  	}
   426  	return platform + "-" + archiveVersion
   427  }
   428  
   429  func archiveUpload(archive string, blobstore string, signer string) error {
   430  	// If signing was requested, generate the signature files
   431  	if signer != "" {
   432  		key := getenvBase64(signer)
   433  		if err := build.PGPSignFile(archive, archive+".asc", string(key)); err != nil {
   434  			return err
   435  		}
   436  	}
   437  	// If uploading to Azure was requested, push the archive possibly with its signature
   438  	if blobstore != "" {
   439  		auth := build.AzureBlobstoreConfig{
   440  			Account:   strings.Split(blobstore, "/")[0],
   441  			Token:     os.Getenv("AZURE_BLOBSTORE_TOKEN"),
   442  			Container: strings.SplitN(blobstore, "/", 2)[1],
   443  		}
   444  		if err := build.AzureBlobstoreUpload(archive, filepath.Base(archive), auth); err != nil {
   445  			return err
   446  		}
   447  		if signer != "" {
   448  			if err := build.AzureBlobstoreUpload(archive+".asc", filepath.Base(archive+".asc"), auth); err != nil {
   449  				return err
   450  			}
   451  		}
   452  	}
   453  	return nil
   454  }
   455  
   456  // skips archiving for some build configurations.
   457  func maybeSkipArchive(env build.Environment) {
   458  	if env.IsPullRequest {
   459  		log.Printf("skipping because this is a PR build")
   460  		os.Exit(0)
   461  	}
   462  	if env.IsCronJob {
   463  		log.Printf("skipping because this is a cron job")
   464  		os.Exit(0)
   465  	}
   466  	if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") {
   467  		log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag)
   468  		os.Exit(0)
   469  	}
   470  }
   471  
   472  // Debian Packaging
   473  func doDebianSource(cmdline []string) {
   474  	var (
   475  		goversion = flag.String("goversion", "", `Go version to build with (will be included in the source package)`)
   476  		gobundle  = flag.String("gobundle", "/tmp/go.tar.gz", `Filesystem path to cache the downloaded Go bundles at`)
   477  		gohash    = flag.String("gohash", "", `SHA256 checksum of the Go sources requested to build with`)
   478  		signer    = flag.String("signer", "", `Signing key name, also used as package author`)
   479  		upload    = flag.String("upload", "", `Where to upload the source package (usually "ethereum/elastos")`)
   480  		sshUser   = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`)
   481  		workdir   = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
   482  		now       = time.Now()
   483  	)
   484  	flag.CommandLine.Parse(cmdline)
   485  	*workdir = makeWorkdir(*workdir)
   486  	env := build.Env()
   487  	maybeSkipArchive(env)
   488  
   489  	// Import the signing key.
   490  	if key := getenvBase64("PPA_SIGNING_KEY"); len(key) > 0 {
   491  		gpg := exec.Command("gpg", "--import")
   492  		gpg.Stdin = bytes.NewReader(key)
   493  		build.MustRun(gpg)
   494  	}
   495  	// Download and verify the Go source package
   496  	if err := build.EnsureGoSources(*goversion, hexutil.MustDecode("0x"+*gohash), *gobundle); err != nil {
   497  		log.Fatalf("Failed to ensure Go source package: %v", err)
   498  	}
   499  	// Create Debian packages and upload them
   500  	for _, pkg := range debPackages {
   501  		for distro, goboot := range debDistroGoBoots {
   502  			// Prepare the debian package with the Elastos.ELA.SideChain.ESC sources
   503  			meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
   504  			pkgdir := stageDebianSource(*workdir, meta)
   505  
   506  			// Ship the Go sources along so we have a proper thing to build with
   507  			if err := build.ExtractTarballArchive(*gobundle, pkgdir); err != nil {
   508  				log.Fatalf("Failed to extract Go sources: %v", err)
   509  			}
   510  			if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil {
   511  				log.Fatalf("Failed to rename Go source folder: %v", err)
   512  			}
   513  			// Run the packaging and upload to the PPA
   514  			debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz")
   515  			debuild.Dir = pkgdir
   516  			build.MustRun(debuild)
   517  
   518  			var (
   519  				basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString())
   520  				source   = filepath.Join(*workdir, basename+".tar.xz")
   521  				dsc      = filepath.Join(*workdir, basename+".dsc")
   522  				changes  = filepath.Join(*workdir, basename+"_source.changes")
   523  			)
   524  			if *signer != "" {
   525  				build.MustRunCommand("debsign", changes)
   526  			}
   527  			if *upload != "" {
   528  				ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes})
   529  			}
   530  		}
   531  	}
   532  }
   533  
   534  func ppaUpload(workdir, ppa, sshUser string, files []string) {
   535  	p := strings.Split(ppa, "/")
   536  	if len(p) != 2 {
   537  		log.Fatal("-upload PPA name must contain single /")
   538  	}
   539  	if sshUser == "" {
   540  		sshUser = p[0]
   541  	}
   542  	incomingDir := fmt.Sprintf("~%s/ubuntu/%s", p[0], p[1])
   543  	// Create the SSH identity file if it doesn't exist.
   544  	var idfile string
   545  	if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 {
   546  		idfile = filepath.Join(workdir, "sshkey")
   547  		if _, err := os.Stat(idfile); os.IsNotExist(err) {
   548  			ioutil.WriteFile(idfile, sshkey, 0600)
   549  		}
   550  	}
   551  	// Upload
   552  	dest := sshUser + "@ppa.launchpad.net"
   553  	if err := build.UploadSFTP(idfile, dest, incomingDir, files); err != nil {
   554  		log.Fatal(err)
   555  	}
   556  }
   557  
   558  func getenvBase64(variable string) []byte {
   559  	dec, err := base64.StdEncoding.DecodeString(os.Getenv(variable))
   560  	if err != nil {
   561  		log.Fatal("invalid base64 " + variable)
   562  	}
   563  	return []byte(dec)
   564  }
   565  
   566  func makeWorkdir(wdflag string) string {
   567  	var err error
   568  	if wdflag != "" {
   569  		err = os.MkdirAll(wdflag, 0744)
   570  	} else {
   571  		wdflag, err = ioutil.TempDir("", "geth-build-")
   572  	}
   573  	if err != nil {
   574  		log.Fatal(err)
   575  	}
   576  	return wdflag
   577  }
   578  
   579  func isUnstableBuild(env build.Environment) bool {
   580  	if env.Tag != "" {
   581  		return false
   582  	}
   583  	return true
   584  }
   585  
   586  type debPackage struct {
   587  	Name        string          // the name of the Debian package to produce, e.g. "ethereum"
   588  	Version     string          // the clean version of the debPackage, e.g. 1.8.12, without any metadata
   589  	Executables []debExecutable // executables to be included in the package
   590  }
   591  
   592  type debMetadata struct {
   593  	Env           build.Environment
   594  	GoBootPackage string
   595  	GoBootPath    string
   596  
   597  	PackageName string
   598  
   599  	// Elastos.ELA.SideChain.ESC version being built. Note that this
   600  	// is not the debian package version. The package version
   601  	// is constructed by VersionString.
   602  	Version string
   603  
   604  	Author       string // "name <email>", also selects signing key
   605  	Distro, Time string
   606  	Executables  []debExecutable
   607  }
   608  
   609  type debExecutable struct {
   610  	PackageName string
   611  	BinaryName  string
   612  	Description string
   613  }
   614  
   615  // Package returns the name of the package if present, or
   616  // fallbacks to BinaryName
   617  func (d debExecutable) Package() string {
   618  	if d.PackageName != "" {
   619  		return d.PackageName
   620  	}
   621  	return d.BinaryName
   622  }
   623  
   624  func newDebMetadata(distro, goboot, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
   625  	if author == "" {
   626  		// No signing key, use default author.
   627  		author = "Ethereum Builds <fjl@ethereum.org>"
   628  	}
   629  	return debMetadata{
   630  		GoBootPackage: goboot,
   631  		GoBootPath:    debGoBootPaths[goboot],
   632  		PackageName:   name,
   633  		Env:           env,
   634  		Author:        author,
   635  		Distro:        distro,
   636  		Version:       version,
   637  		Time:          t.Format(time.RFC1123Z),
   638  		Executables:   exes,
   639  	}
   640  }
   641  
   642  // Name returns the name of the metapackage that depends
   643  // on all executable packages.
   644  func (meta debMetadata) Name() string {
   645  	if isUnstableBuild(meta.Env) {
   646  		return meta.PackageName + "-unstable"
   647  	}
   648  	return meta.PackageName
   649  }
   650  
   651  // VersionString returns the debian version of the packages.
   652  func (meta debMetadata) VersionString() string {
   653  	vsn := meta.Version
   654  	if meta.Env.Buildnum != "" {
   655  		vsn += "+build" + meta.Env.Buildnum
   656  	}
   657  	if meta.Distro != "" {
   658  		vsn += "+" + meta.Distro
   659  	}
   660  	return vsn
   661  }
   662  
   663  // ExeList returns the list of all executable packages.
   664  func (meta debMetadata) ExeList() string {
   665  	names := make([]string, len(meta.Executables))
   666  	for i, e := range meta.Executables {
   667  		names[i] = meta.ExeName(e)
   668  	}
   669  	return strings.Join(names, ", ")
   670  }
   671  
   672  // ExeName returns the package name of an executable package.
   673  func (meta debMetadata) ExeName(exe debExecutable) string {
   674  	if isUnstableBuild(meta.Env) {
   675  		return exe.Package() + "-unstable"
   676  	}
   677  	return exe.Package()
   678  }
   679  
   680  // ExeConflicts returns the content of the Conflicts field
   681  // for executable packages.
   682  func (meta debMetadata) ExeConflicts(exe debExecutable) string {
   683  	if isUnstableBuild(meta.Env) {
   684  		// Set up the conflicts list so that the *-unstable packages
   685  		// cannot be installed alongside the regular version.
   686  		//
   687  		// https://www.debian.org/doc/debian-policy/ch-relationships.html
   688  		// is very explicit about Conflicts: and says that Breaks: should
   689  		// be preferred and the conflicting files should be handled via
   690  		// alternates. We might do this eventually but using a conflict is
   691  		// easier now.
   692  		return "ethereum, " + exe.Package()
   693  	}
   694  	return ""
   695  }
   696  
   697  func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
   698  	pkg := meta.Name() + "-" + meta.VersionString()
   699  	pkgdir = filepath.Join(tmpdir, pkg)
   700  	if err := os.Mkdir(pkgdir, 0755); err != nil {
   701  		log.Fatal(err)
   702  	}
   703  	// Copy the source code.
   704  	build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator))
   705  
   706  	// Put the debian build files in place.
   707  	debian := filepath.Join(pkgdir, "debian")
   708  	build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
   709  	build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
   710  	build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta)
   711  	build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
   712  	build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
   713  	build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
   714  	for _, exe := range meta.Executables {
   715  		install := filepath.Join(debian, meta.ExeName(exe)+".install")
   716  		docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
   717  		build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe)
   718  		build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe)
   719  	}
   720  	return pkgdir
   721  }
   722  
   723  // Windows installer
   724  func doWindowsInstaller(cmdline []string) {
   725  	// Parse the flags and make skip installer generation on PRs
   726  	var (
   727  		arch    = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
   728  		signer  = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
   729  		upload  = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
   730  		workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
   731  	)
   732  	flag.CommandLine.Parse(cmdline)
   733  	*workdir = makeWorkdir(*workdir)
   734  	env := build.Env()
   735  	maybeSkipArchive(env)
   736  
   737  	// Aggregate binaries that are included in the installer
   738  	var (
   739  		devTools []string
   740  		allTools []string
   741  		gethTool string
   742  	)
   743  	for _, file := range allToolsArchiveFiles {
   744  		if file == "COPYING" { // license, copied later
   745  			continue
   746  		}
   747  		allTools = append(allTools, filepath.Base(file))
   748  		if filepath.Base(file) == "geth.exe" {
   749  			gethTool = file
   750  		} else {
   751  			devTools = append(devTools, file)
   752  		}
   753  	}
   754  
   755  	// Render NSIS scripts: Installer NSIS contains two installer sections,
   756  	// first section contains the geth binary, second section holds the dev tools.
   757  	templateData := map[string]interface{}{
   758  		"License":  "COPYING",
   759  		"Geth":     gethTool,
   760  		"DevTools": devTools,
   761  	}
   762  	build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil)
   763  	build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData)
   764  	build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools)
   765  	build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil)
   766  	build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil)
   767  	build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0755)
   768  	build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0755)
   769  
   770  	// Build the installer. This assumes that all the needed files have been previously
   771  	// built (don't mix building and packaging to keep cross compilation complexity to a
   772  	// minimum).
   773  	version := strings.Split(params.Version, ".")
   774  	if env.Commit != "" {
   775  		version[2] += "-" + env.Commit[:8]
   776  	}
   777  	installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe")
   778  	build.MustRunCommand("makensis.exe",
   779  		"/DOUTPUTFILE="+installer,
   780  		"/DMAJORVERSION="+version[0],
   781  		"/DMINORVERSION="+version[1],
   782  		"/DBUILDVERSION="+version[2],
   783  		"/DARCH="+*arch,
   784  		filepath.Join(*workdir, "geth.nsi"),
   785  	)
   786  
   787  	// Sign and publish installer.
   788  	if err := archiveUpload(installer, *upload, *signer); err != nil {
   789  		log.Fatal(err)
   790  	}
   791  }
   792  
   793  // Android archives
   794  
   795  func doAndroidArchive(cmdline []string) {
   796  	var (
   797  		local  = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
   798  		signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`)
   799  		deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`)
   800  		upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`)
   801  	)
   802  	flag.CommandLine.Parse(cmdline)
   803  	env := build.Env()
   804  
   805  	// Sanity check that the SDK and NDK are installed and set
   806  	if os.Getenv("ANDROID_HOME") == "" {
   807  		log.Fatal("Please ensure ANDROID_HOME points to your Android SDK")
   808  	}
   809  	// Build the Android archive and Maven resources
   810  	build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
   811  	build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/elastos/Elastos.ELA.SideChain.ESC/mobile"))
   812  
   813  	if *local {
   814  		// If we're building locally, copy bundle to build dir and skip Maven
   815  		os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar"))
   816  		return
   817  	}
   818  	meta := newMavenMetadata(env)
   819  	build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta)
   820  
   821  	// Skip Maven deploy and Azure upload for PR builds
   822  	maybeSkipArchive(env)
   823  
   824  	// Sign and upload the archive to Azure
   825  	archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar"
   826  	os.Rename("geth.aar", archive)
   827  
   828  	if err := archiveUpload(archive, *upload, *signer); err != nil {
   829  		log.Fatal(err)
   830  	}
   831  	// Sign and upload all the artifacts to Maven Central
   832  	os.Rename(archive, meta.Package+".aar")
   833  	if *signer != "" && *deploy != "" {
   834  		// Import the signing key into the local GPG instance
   835  		key := getenvBase64(*signer)
   836  		gpg := exec.Command("gpg", "--import")
   837  		gpg.Stdin = bytes.NewReader(key)
   838  		build.MustRun(gpg)
   839  		keyID, err := build.PGPKeyID(string(key))
   840  		if err != nil {
   841  			log.Fatal(err)
   842  		}
   843  		// Upload the artifacts to Sonatype and/or Maven Central
   844  		repo := *deploy + "/service/local/staging/deploy/maven2"
   845  		if meta.Develop {
   846  			repo = *deploy + "/content/repositories/snapshots"
   847  		}
   848  		build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X",
   849  			"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
   850  			"-Dgpg.keyname="+keyID,
   851  			"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
   852  	}
   853  }
   854  
   855  func gomobileTool(subcmd string, args ...string) *exec.Cmd {
   856  	cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd)
   857  	cmd.Args = append(cmd.Args, args...)
   858  	cmd.Env = []string{
   859  		"GOPATH=" + build.GOPATH(),
   860  		"PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"),
   861  	}
   862  	for _, e := range os.Environ() {
   863  		if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") {
   864  			continue
   865  		}
   866  		cmd.Env = append(cmd.Env, e)
   867  	}
   868  	return cmd
   869  }
   870  
   871  type mavenMetadata struct {
   872  	Version      string
   873  	Package      string
   874  	Develop      bool
   875  	Contributors []mavenContributor
   876  }
   877  
   878  type mavenContributor struct {
   879  	Name  string
   880  	Email string
   881  }
   882  
   883  func newMavenMetadata(env build.Environment) mavenMetadata {
   884  	// Collect the list of authors from the repo root
   885  	contribs := []mavenContributor{}
   886  	if authors, err := os.Open("AUTHORS"); err == nil {
   887  		defer authors.Close()
   888  
   889  		scanner := bufio.NewScanner(authors)
   890  		for scanner.Scan() {
   891  			// Skip any whitespace from the authors list
   892  			line := strings.TrimSpace(scanner.Text())
   893  			if line == "" || line[0] == '#' {
   894  				continue
   895  			}
   896  			// Split the author and insert as a contributor
   897  			re := regexp.MustCompile("([^<]+) <(.+)>")
   898  			parts := re.FindStringSubmatch(line)
   899  			if len(parts) == 3 {
   900  				contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]})
   901  			}
   902  		}
   903  	}
   904  	// Render the version and package strings
   905  	version := params.Version
   906  	if isUnstableBuild(env) {
   907  		version += "-SNAPSHOT"
   908  	}
   909  	return mavenMetadata{
   910  		Version:      version,
   911  		Package:      "geth-" + version,
   912  		Develop:      isUnstableBuild(env),
   913  		Contributors: contribs,
   914  	}
   915  }
   916  
   917  // XCode frameworks
   918  
   919  func doXCodeFramework(cmdline []string) {
   920  	var (
   921  		local  = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`)
   922  		signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`)
   923  		deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`)
   924  		upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
   925  	)
   926  	flag.CommandLine.Parse(cmdline)
   927  	env := build.Env()
   928  
   929  	// Build the iOS XCode framework
   930  	build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
   931  	build.MustRun(gomobileTool("init"))
   932  	bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/elastos/Elastos.ELA.SideChain.ESC/mobile")
   933  
   934  	if *local {
   935  		// If we're building locally, use the build folder and stop afterwards
   936  		bind.Dir, _ = filepath.Abs(GOBIN)
   937  		build.MustRun(bind)
   938  		return
   939  	}
   940  	archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit))
   941  	if err := os.Mkdir(archive, os.ModePerm); err != nil {
   942  		log.Fatal(err)
   943  	}
   944  	bind.Dir, _ = filepath.Abs(archive)
   945  	build.MustRun(bind)
   946  	build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive)
   947  
   948  	// Skip CocoaPods deploy and Azure upload for PR builds
   949  	maybeSkipArchive(env)
   950  
   951  	// Sign and upload the framework to Azure
   952  	if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil {
   953  		log.Fatal(err)
   954  	}
   955  	// Prepare and upload a PodSpec to CocoaPods
   956  	if *deploy != "" {
   957  		meta := newPodMetadata(env, archive)
   958  		build.Render("build/pod.podspec", "Geth.podspec", 0755, meta)
   959  		build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings", "--verbose")
   960  	}
   961  }
   962  
   963  type podMetadata struct {
   964  	Version      string
   965  	Commit       string
   966  	Archive      string
   967  	Contributors []podContributor
   968  }
   969  
   970  type podContributor struct {
   971  	Name  string
   972  	Email string
   973  }
   974  
   975  func newPodMetadata(env build.Environment, archive string) podMetadata {
   976  	// Collect the list of authors from the repo root
   977  	contribs := []podContributor{}
   978  	if authors, err := os.Open("AUTHORS"); err == nil {
   979  		defer authors.Close()
   980  
   981  		scanner := bufio.NewScanner(authors)
   982  		for scanner.Scan() {
   983  			// Skip any whitespace from the authors list
   984  			line := strings.TrimSpace(scanner.Text())
   985  			if line == "" || line[0] == '#' {
   986  				continue
   987  			}
   988  			// Split the author and insert as a contributor
   989  			re := regexp.MustCompile("([^<]+) <(.+)>")
   990  			parts := re.FindStringSubmatch(line)
   991  			if len(parts) == 3 {
   992  				contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]})
   993  			}
   994  		}
   995  	}
   996  	version := params.Version
   997  	if isUnstableBuild(env) {
   998  		version += "-unstable." + env.Buildnum
   999  	}
  1000  	return podMetadata{
  1001  		Archive:      archive,
  1002  		Version:      version,
  1003  		Commit:       env.Commit,
  1004  		Contributors: contribs,
  1005  	}
  1006  }
  1007  
  1008  // Cross compilation
  1009  
  1010  func doXgo(cmdline []string) {
  1011  	var (
  1012  		alltools = flag.Bool("alltools", false, `Flag whether we're building all known tools, or only on in particular`)
  1013  	)
  1014  	flag.CommandLine.Parse(cmdline)
  1015  	env := build.Env()
  1016  
  1017  	// Make sure xgo is available for cross compilation
  1018  	gogetxgo := goTool("get", "github.com/karalabe/xgo")
  1019  	build.MustRun(gogetxgo)
  1020  
  1021  	// If all tools building is requested, build everything the builder wants
  1022  	args := append(buildFlags(env), flag.Args()...)
  1023  
  1024  	if *alltools {
  1025  		args = append(args, []string{"--dest", GOBIN}...)
  1026  		for _, res := range allToolsArchiveFiles {
  1027  			if strings.HasPrefix(res, GOBIN) {
  1028  				// Binary tool found, cross build it explicitly
  1029  				args = append(args, "./"+filepath.Join("cmd", filepath.Base(res)))
  1030  				xgo := xgoTool(args)
  1031  				build.MustRun(xgo)
  1032  				args = args[:len(args)-1]
  1033  			}
  1034  		}
  1035  		return
  1036  	}
  1037  	// Otherwise xxecute the explicit cross compilation
  1038  	path := args[len(args)-1]
  1039  	args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...)
  1040  
  1041  	xgo := xgoTool(args)
  1042  	build.MustRun(xgo)
  1043  }
  1044  
  1045  func xgoTool(args []string) *exec.Cmd {
  1046  	cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...)
  1047  	cmd.Env = []string{
  1048  		"GOPATH=" + build.GOPATH(),
  1049  		"GOBIN=" + GOBIN,
  1050  	}
  1051  	for _, e := range os.Environ() {
  1052  		if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
  1053  			continue
  1054  		}
  1055  		cmd.Env = append(cmd.Env, e)
  1056  	}
  1057  	return cmd
  1058  }
  1059  
  1060  // Binary distribution cleanups
  1061  
  1062  func doPurge(cmdline []string) {
  1063  	var (
  1064  		store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`)
  1065  		limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`)
  1066  	)
  1067  	flag.CommandLine.Parse(cmdline)
  1068  
  1069  	if env := build.Env(); !env.IsCronJob {
  1070  		log.Printf("skipping because not a cron job")
  1071  		os.Exit(0)
  1072  	}
  1073  	// Create the azure authentication and list the current archives
  1074  	auth := build.AzureBlobstoreConfig{
  1075  		Account:   strings.Split(*store, "/")[0],
  1076  		Token:     os.Getenv("AZURE_BLOBSTORE_TOKEN"),
  1077  		Container: strings.SplitN(*store, "/", 2)[1],
  1078  	}
  1079  	blobs, err := build.AzureBlobstoreList(auth)
  1080  	if err != nil {
  1081  		log.Fatal(err)
  1082  	}
  1083  	// Iterate over the blobs, collect and sort all unstable builds
  1084  	for i := 0; i < len(blobs); i++ {
  1085  		if !strings.Contains(blobs[i].Name, "unstable") {
  1086  			blobs = append(blobs[:i], blobs[i+1:]...)
  1087  			i--
  1088  		}
  1089  	}
  1090  	for i := 0; i < len(blobs); i++ {
  1091  		for j := i + 1; j < len(blobs); j++ {
  1092  			if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) {
  1093  				blobs[i], blobs[j] = blobs[j], blobs[i]
  1094  			}
  1095  		}
  1096  	}
  1097  	// Filter out all archives more recent that the given threshold
  1098  	for i, blob := range blobs {
  1099  		if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour {
  1100  			blobs = blobs[:i]
  1101  			break
  1102  		}
  1103  	}
  1104  	// Delete all marked as such and return
  1105  	if err := build.AzureBlobstoreDelete(auth, blobs); err != nil {
  1106  		log.Fatal(err)
  1107  	}
  1108  }