github.com/core-coin/go-core/v2@v2.1.9/build/ci.go (about)

     1  // Copyright 2016 The go-core Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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 go-core 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 go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  /*
    18  The ci command is called from Continuous Integration scripts.
    19  
    20  Usage: go run build/ci.go <command> <command flags/arguments>
    21  
    22  Available commands are:
    23  
    24  	install    [ -arch architecture ] [ -cc compiler ] [ packages... ]                          -- builds packages and executables
    25  	test       [ -coverage ] [ packages... ]                                                    -- runs the tests
    26  	lint                                                                                        -- runs certain pre-selected linters
    27  	archive    [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts
    28  	importkeys                                                                                  -- imports signing keys from env
    29  	debsrc     [ -signer key-id ] [ -upload dest ]                                              -- creates a debian source package
    30  	nsis                                                                                        -- creates a Windows NSIS installer
    31  	aar        [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ]                      -- creates an Android archive
    32  	xcode      [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ]                      -- creates an iOS XCode framework
    33  	xgo        [ -alltools ] [ options ]                                                        -- cross builds according to options
    34  	purge      [ -store blobstore ] [ -days threshold ]                                         -- purges old archives from the blobstore
    35  
    36  For all commands, -n prevents execution of external programs (dry run mode).
    37  */
    38  package main
    39  
    40  import (
    41  	"bytes"
    42  	"flag"
    43  	"fmt"
    44  	"io"
    45  	"log"
    46  	"os"
    47  	"os/exec"
    48  	"path"
    49  	"path/filepath"
    50  	"runtime"
    51  	"strings"
    52  	"time"
    53  
    54  	"github.com/core-coin/go-core/v2/internal/build"
    55  )
    56  
    57  var (
    58  	// Files that end up in the gocore*.zip archive.
    59  	gocoreArchiveFiles = []string{
    60  		"COPYING",
    61  		executablePath("gocore"),
    62  	}
    63  
    64  	// Files that end up in the gocore-alltools*.zip archive.
    65  	allToolsArchiveFiles = []string{
    66  		"COPYING",
    67  		executablePath("abigen"),
    68  		executablePath("bootnode"),
    69  		executablePath("cvm"),
    70  		executablePath("gocore"),
    71  		executablePath("rlpdump"),
    72  		executablePath("clef"),
    73  	}
    74  	// This is the version of go that will be downloaded by
    75  	//
    76  	//     go run ci.go install -dlgo
    77  	dlgoVersion = "1.15.6"
    78  )
    79  
    80  var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
    81  
    82  func executablePath(name string) string {
    83  	if runtime.GOOS == "windows" {
    84  		name += ".exe"
    85  	}
    86  	return filepath.Join(GOBIN, name)
    87  }
    88  
    89  func main() {
    90  	log.SetFlags(log.Lshortfile)
    91  
    92  	if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) {
    93  		log.Fatal("this script must be run from the root of the repository")
    94  	}
    95  	if len(os.Args) < 2 {
    96  		log.Fatal("need subcommand as first argument")
    97  	}
    98  	switch os.Args[1] {
    99  	case "install":
   100  		doInstall(os.Args[2:])
   101  	case "test":
   102  		doTest(os.Args[2:])
   103  	case "lint":
   104  		doLint(os.Args[2:])
   105  	case "xgo":
   106  		doXgo(os.Args[2:])
   107  	case "purge":
   108  		doPurge(os.Args[2:])
   109  	default:
   110  		log.Fatal("unknown command ", os.Args[1])
   111  	}
   112  }
   113  
   114  // Compiling
   115  
   116  func doInstall(cmdline []string) {
   117  	var (
   118  		dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
   119  		arch = flag.String("arch", "", "Architecture to cross build for")
   120  		cc   = flag.String("cc", "", "C compiler to cross build with")
   121  	)
   122  	flag.CommandLine.Parse(cmdline)
   123  	env := build.Env()
   124  
   125  	// Check local Go version. People regularly open issues about compilation
   126  	// failure with outdated Go. This should save them the trouble.
   127  	if !strings.Contains(runtime.Version(), "devel") {
   128  		// Figure out the minor version number since we can't textually compare (1.10 < 1.9)
   129  		var minor int
   130  		fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
   131  		if minor < 13 {
   132  			log.Println("You have Go version", runtime.Version())
   133  			log.Println("go-core requires at least Go version 1.13 and cannot")
   134  			log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
   135  			os.Exit(1)
   136  		}
   137  	}
   138  
   139  	// Choose which go command we're going to use.
   140  	var gobuild *exec.Cmd
   141  	if !*dlgo {
   142  		// Default behavior: use the go version which runs ci.go right now.
   143  		gobuild = goTool("build")
   144  	} else {
   145  		// Download of Go requested. This is for build environments where the
   146  		// installed version is too old and cannot be upgraded easily.
   147  		cachedir := filepath.Join("build", "cache")
   148  		goroot := downloadGo(runtime.GOARCH, runtime.GOOS, cachedir)
   149  		gobuild = localGoTool(goroot, "build")
   150  	}
   151  
   152  	// Configure environment for cross build.
   153  	if *arch != "" && *arch != runtime.GOARCH {
   154  		gobuild.Env = append(gobuild.Env, "CGO_ENABLED=1")
   155  		gobuild.Env = append(gobuild.Env, "GOARCH="+*arch)
   156  	}
   157  
   158  	// Configure C compiler.
   159  	if *cc != "" {
   160  		gobuild.Env = append(gobuild.Env, "CC="+*cc)
   161  	} else if os.Getenv("CC") != "" {
   162  		gobuild.Env = append(gobuild.Env, "CC="+os.Getenv("CC"))
   163  	}
   164  
   165  	// arm64 CI builders are memory-constrained and can't handle concurrent builds,
   166  	// better disable it. This check isn't the best, it should probably
   167  	// check for something in env instead.
   168  	if runtime.GOARCH == "arm64" {
   169  		gobuild.Args = append(gobuild.Args, "-p", "1")
   170  	}
   171  
   172  	// Put the default settings in.
   173  	gobuild.Args = append(gobuild.Args, buildFlags(env)...)
   174  
   175  	// We use -trimpath to avoid leaking local paths into the built executables.
   176  	gobuild.Args = append(gobuild.Args, "-trimpath")
   177  
   178  	// Show packages during build.
   179  	gobuild.Args = append(gobuild.Args, "-v")
   180  
   181  	if runtime.GOOS == "windows" {
   182  		gobuild.Args = append(gobuild.Args, "-buildmode=exe")
   183  	}
   184  	// Now we choose what we're even building.
   185  	// Default: collect all 'main' packages in cmd/ and build those.
   186  	packages := flag.Args()
   187  	if len(packages) == 0 {
   188  		packages = build.FindMainPackages("./cmd")
   189  	}
   190  
   191  	// Do the build!
   192  	for _, pkg := range packages {
   193  		args := make([]string, len(gobuild.Args))
   194  		copy(args, gobuild.Args)
   195  		args = append(args, "-o", executablePath(path.Base(pkg)))
   196  		args = append(args, pkg)
   197  		build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env})
   198  	}
   199  }
   200  
   201  // buildFlags returns the go tool flags for building.
   202  func buildFlags(env build.Environment) (flags []string) {
   203  	var ld []string
   204  	if env.Commit != "" {
   205  		ld = append(ld, "-X", "main.gitTag="+env.Tag)
   206  		ld = append(ld, "-X", "main.gitCommit="+env.Commit)
   207  		ld = append(ld, "-X", "main.gitDate="+env.Date)
   208  	}
   209  	// Strip DWARF on darwin. This used to be required for certain things,
   210  	// and there is no downside to this, so we just keep doing it.
   211  	if runtime.GOOS == "darwin" {
   212  		ld = append(ld, "-s")
   213  	}
   214  	if len(ld) > 0 {
   215  		flags = append(flags, "-ldflags", strings.Join(ld, " "))
   216  	}
   217  	return flags
   218  }
   219  
   220  // goTool returns the go tool. This uses the Go version which runs ci.go.
   221  func goTool(subcmd string, args ...string) *exec.Cmd {
   222  	cmd := build.GoTool(subcmd, args...)
   223  	goToolSetEnv(cmd)
   224  	return cmd
   225  }
   226  
   227  // localGoTool returns the go tool from the given GOROOT.
   228  func localGoTool(goroot string, subcmd string, args ...string) *exec.Cmd {
   229  	gotool := filepath.Join(goroot, "bin", "go")
   230  	cmd := exec.Command(gotool, subcmd)
   231  	goToolSetEnv(cmd)
   232  	cmd.Env = append(cmd.Env, "GOROOT="+goroot)
   233  	cmd.Args = append(cmd.Args, args...)
   234  	return cmd
   235  }
   236  
   237  // goToolSetEnv forwards the build environment to the go tool.
   238  func goToolSetEnv(cmd *exec.Cmd) {
   239  	cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
   240  	for _, e := range os.Environ() {
   241  		if strings.HasPrefix(e, "GOBIN=") || strings.HasPrefix(e, "CC=") {
   242  			continue
   243  		}
   244  		cmd.Env = append(cmd.Env, e)
   245  	}
   246  }
   247  
   248  // Running The Tests
   249  //
   250  // "tests" also includes static analysis tools such as vet.
   251  
   252  func doTest(cmdline []string) {
   253  	coverage := flag.Bool("coverage", false, "Whether to record code coverage")
   254  	verbose := flag.Bool("v", false, "Whether to log verbosely")
   255  	flag.CommandLine.Parse(cmdline)
   256  	env := build.Env()
   257  
   258  	packages := []string{"./..."}
   259  	if len(flag.CommandLine.Args()) > 0 {
   260  		packages = flag.CommandLine.Args()
   261  	}
   262  
   263  	// Run the actual tests.
   264  	// Test a single package at a time. CI builders are slow
   265  	// and some tests run into timeouts under load.
   266  	gotest := goTool("test", buildFlags(env)...)
   267  	gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "20m")
   268  	if *coverage {
   269  		gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
   270  	}
   271  	if *verbose {
   272  		gotest.Args = append(gotest.Args, "-v")
   273  	}
   274  
   275  	gotest.Args = append(gotest.Args, packages...)
   276  
   277  	// windows sometimes fails to remove build temp dir
   278  	if runtime.GOOS == "windows" {
   279  		fmt.Println(">>>", strings.Join(gotest.Args, " "))
   280  		var buf bytes.Buffer
   281  		gotest.Stderr = &buf
   282  		gotest.Stdout = &buf
   283  
   284  		err := gotest.Run()
   285  		res := buf.String()
   286  		fmt.Println(res)
   287  		if err != nil {
   288  			if strings.Contains(res, "Access is denied") && !strings.Contains(res, "FAIL") {
   289  				return
   290  			} else {
   291  				log.Fatal(err)
   292  			}
   293  		} else {
   294  			return
   295  		}
   296  	}
   297  
   298  	build.MustRun(gotest)
   299  }
   300  
   301  // doLint runs golangci-lint on requested packages.
   302  func doLint(cmdline []string) {
   303  	var (
   304  		cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.")
   305  	)
   306  	flag.CommandLine.Parse(cmdline)
   307  	packages := []string{"./..."}
   308  	if len(flag.CommandLine.Args()) > 0 {
   309  		packages = flag.CommandLine.Args()
   310  	}
   311  
   312  	linter := downloadLinter(*cachedir)
   313  	lflags := []string{"run", "--config", ".golangci.yml"}
   314  	build.MustRunCommand(linter, append(lflags, packages...)...)
   315  	fmt.Println("You have achieved perfection.")
   316  }
   317  
   318  // downloadLinter downloads and unpacks golangci-lint.
   319  func downloadLinter(cachedir string) string {
   320  	const version = "1.27.0"
   321  
   322  	csdb := build.MustLoadChecksums("build/checksums.txt")
   323  	base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH)
   324  	url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s.tar.gz", version, base)
   325  	archivePath := filepath.Join(cachedir, base+".tar.gz")
   326  	if err := csdb.DownloadFile(url, archivePath); err != nil {
   327  		log.Fatal(err)
   328  	}
   329  	if err := build.ExtractArchive(archivePath, cachedir); err != nil {
   330  		log.Fatal(err)
   331  	}
   332  	return filepath.Join(cachedir, base, "golangci-lint")
   333  }
   334  
   335  // downloadGoSources downloads the Go source tarball.
   336  func downloadGoSources(cachedir string) string {
   337  	csdb := build.MustLoadChecksums("build/checksums.txt")
   338  	file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion)
   339  	url := "https://dl.google.com/go/" + file
   340  	dst := filepath.Join(cachedir, file)
   341  	if err := csdb.DownloadFile(url, dst); err != nil {
   342  		log.Fatal(err)
   343  	}
   344  	return dst
   345  }
   346  
   347  // downloadGo downloads the Go binary distribution and unpacks it into a temporary
   348  // directory. It returns the GOROOT of the unpacked toolchain.
   349  func downloadGo(goarch, goos, cachedir string) string {
   350  	if goarch == "arm" {
   351  		goarch = "armv6l"
   352  	}
   353  
   354  	csdb := build.MustLoadChecksums("build/checksums.txt")
   355  	file := fmt.Sprintf("go%s.%s-%s", dlgoVersion, goos, goarch)
   356  	if goos == "windows" {
   357  		file += ".zip"
   358  	} else {
   359  		file += ".tar.gz"
   360  	}
   361  	url := "https://golang.org/dl/" + file
   362  	dst := filepath.Join(cachedir, file)
   363  	if err := csdb.DownloadFile(url, dst); err != nil {
   364  		log.Fatal(err)
   365  	}
   366  
   367  	ucache, err := os.UserCacheDir()
   368  	if err != nil {
   369  		log.Fatal(err)
   370  	}
   371  	godir := filepath.Join(ucache, fmt.Sprintf("gocore-go-%s-%s-%s", dlgoVersion, goos, goarch))
   372  	if err := build.ExtractArchive(dst, godir); err != nil {
   373  		log.Fatal(err)
   374  	}
   375  	goroot, err := filepath.Abs(filepath.Join(godir, "go"))
   376  	if err != nil {
   377  		log.Fatal(err)
   378  	}
   379  	return goroot
   380  }
   381  
   382  // Cross compilation
   383  
   384  func doXgo(cmdline []string) {
   385  	var (
   386  		alltools = flag.Bool("alltools", false, `Flag whether we're building all known tools, or only on in particular`)
   387  	)
   388  	flag.CommandLine.Parse(cmdline)
   389  	env := build.Env()
   390  
   391  	// Make sure xgo is available for cross compilation
   392  	gogetxgo := goTool("get", "github.com/karalabe/xgo")
   393  	build.MustRun(gogetxgo)
   394  
   395  	// If all tools building is requested, build everything the builder wants
   396  	args := append(buildFlags(env), flag.Args()...)
   397  
   398  	if *alltools {
   399  		args = append(args, []string{"--dest", GOBIN}...)
   400  		for _, res := range allToolsArchiveFiles {
   401  			if strings.HasPrefix(res, GOBIN) {
   402  				// Binary tool found, cross build it explicitly
   403  				args = append(args, "./"+filepath.Join("cmd", filepath.Base(res)))
   404  				xgo := xgoTool(args)
   405  				build.MustRun(xgo)
   406  				args = args[:len(args)-1]
   407  			}
   408  		}
   409  		return
   410  	}
   411  	// Otherwise xxecute the explicit cross compilation
   412  	path := args[len(args)-1]
   413  	args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...)
   414  
   415  	xgo := xgoTool(args)
   416  	build.MustRun(xgo)
   417  }
   418  
   419  func xgoTool(args []string) *exec.Cmd {
   420  	cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...)
   421  	cmd.Env = os.Environ()
   422  	cmd.Env = append(cmd.Env, []string{
   423  		"GOBIN=" + GOBIN,
   424  	}...)
   425  	return cmd
   426  }
   427  
   428  // Binary distribution cleanups
   429  
   430  func doPurge(cmdline []string) {
   431  	var (
   432  		store = flag.String("store", "", `Destination from where to purge archives (usually "gocorestore/builds")`)
   433  		limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`)
   434  	)
   435  	flag.CommandLine.Parse(cmdline)
   436  
   437  	if env := build.Env(); !env.IsCronJob {
   438  		log.Printf("skipping because not a cron job")
   439  		os.Exit(0)
   440  	}
   441  	// Create the azure authentication and list the current archives
   442  	auth := build.AzureBlobstoreConfig{
   443  		Account:   strings.Split(*store, "/")[0],
   444  		Token:     os.Getenv("AZURE_BLOBSTORE_TOKEN"),
   445  		Container: strings.SplitN(*store, "/", 2)[1],
   446  	}
   447  	blobs, err := build.AzureBlobstoreList(auth)
   448  	if err != nil {
   449  		log.Fatal(err)
   450  	}
   451  	fmt.Printf("Found %d blobs\n", len(blobs))
   452  
   453  	// Iterate over the blobs, collect and sort all unstable builds
   454  	for i := 0; i < len(blobs); i++ {
   455  		if !strings.Contains(blobs[i].Name, "unstable") {
   456  			blobs = append(blobs[:i], blobs[i+1:]...)
   457  			i--
   458  		}
   459  	}
   460  	for i := 0; i < len(blobs); i++ {
   461  		for j := i + 1; j < len(blobs); j++ {
   462  			if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) {
   463  				blobs[i], blobs[j] = blobs[j], blobs[i]
   464  			}
   465  		}
   466  	}
   467  	// Filter out all archives more recent that the given threshold
   468  	for i, blob := range blobs {
   469  		if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour {
   470  			blobs = blobs[:i]
   471  			break
   472  		}
   473  	}
   474  	fmt.Printf("Deleting %d blobs\n", len(blobs))
   475  	// Delete all marked as such and return
   476  	if err := build.AzureBlobstoreDelete(auth, blobs); err != nil {
   477  		log.Fatal(err)
   478  	}
   479  }
   480  
   481  func captureStdout(f func()) string {
   482  	old := os.Stdout
   483  	r, w, _ := os.Pipe()
   484  	os.Stdout = w
   485  
   486  	f()
   487  
   488  	w.Close()
   489  	os.Stdout = old
   490  
   491  	var buf bytes.Buffer
   492  	io.Copy(&buf, r)
   493  	return buf.String()
   494  }