github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/build/ci.go (about)

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