github.com/Consensys/quorum@v21.1.0+incompatible/build/ci.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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-ethereum 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-ethereum 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/ethereum/go-ethereum/common/hexutil" 62 "github.com/ethereum/go-ethereum/internal/build" 63 "github.com/ethereum/go-ethereum/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("go-ethereum 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 // Quorum 326 // Ignore not Quorum related packages to accelerate build 327 packages = build.ExpandPackagesNoVendor(packages) 328 packages = build.IgnorePackages(packages) 329 330 // Run the actual tests. 331 // Test a single package at a time. CI builders are slow 332 // and some tests run into timeouts under load. 333 gotest := goTool("test", buildFlags(env)...) 334 gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "5m", "--short") 335 if *coverage { 336 gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") 337 } 338 339 gotest.Args = append(gotest.Args, packages...) 340 build.MustRun(gotest) 341 } 342 343 // runs gometalinter on requested packages 344 func doLint(cmdline []string) { 345 flag.CommandLine.Parse(cmdline) 346 347 packages := []string{"./..."} 348 if len(flag.CommandLine.Args()) > 0 { 349 packages = flag.CommandLine.Args() 350 } 351 // Get metalinter and install all supported linters 352 build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v2")) 353 build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), "--install") 354 355 // Run fast linters batched together 356 configs := []string{ 357 "--vendor", 358 "--tests", 359 "--deadline=2m", 360 "--disable-all", 361 "--enable=goimports", 362 "--enable=varcheck", 363 "--enable=vet", 364 "--enable=gofmt", 365 "--enable=misspell", 366 "--enable=goconst", 367 "--min-occurrences=6", // for goconst 368 } 369 build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...) 370 371 // Run slow linters one by one 372 for _, linter := range []string{"unconvert", "gosimple"} { 373 configs = []string{"--vendor", "--tests", "--deadline=10m", "--disable-all", "--enable=" + linter} 374 build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...) 375 } 376 } 377 378 // Release Packaging 379 func doArchive(cmdline []string) { 380 var ( 381 arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") 382 atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") 383 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) 384 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 385 ext string 386 ) 387 flag.CommandLine.Parse(cmdline) 388 switch *atype { 389 case "zip": 390 ext = ".zip" 391 case "tar": 392 ext = ".tar.gz" 393 default: 394 log.Fatal("unknown archive type: ", atype) 395 } 396 397 var ( 398 env = build.Env() 399 400 basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit)) 401 geth = "geth-" + basegeth + ext 402 alltools = "geth-alltools-" + basegeth + ext 403 ) 404 maybeSkipArchive(env) 405 if err := build.WriteArchive(geth, gethArchiveFiles); err != nil { 406 log.Fatal(err) 407 } 408 if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil { 409 log.Fatal(err) 410 } 411 for _, archive := range []string{geth, alltools} { 412 if err := archiveUpload(archive, *upload, *signer); err != nil { 413 log.Fatal(err) 414 } 415 } 416 } 417 418 func archiveBasename(arch string, archiveVersion string) string { 419 platform := runtime.GOOS + "-" + arch 420 if arch == "arm" { 421 platform += os.Getenv("GOARM") 422 } 423 if arch == "android" { 424 platform = "android-all" 425 } 426 if arch == "ios" { 427 platform = "ios-all" 428 } 429 return platform + "-" + archiveVersion 430 } 431 432 func archiveUpload(archive string, blobstore string, signer string) error { 433 // If signing was requested, generate the signature files 434 if signer != "" { 435 key := getenvBase64(signer) 436 if err := build.PGPSignFile(archive, archive+".asc", string(key)); err != nil { 437 return err 438 } 439 } 440 // If uploading to Azure was requested, push the archive possibly with its signature 441 if blobstore != "" { 442 auth := build.AzureBlobstoreConfig{ 443 Account: strings.Split(blobstore, "/")[0], 444 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 445 Container: strings.SplitN(blobstore, "/", 2)[1], 446 } 447 if err := build.AzureBlobstoreUpload(archive, filepath.Base(archive), auth); err != nil { 448 return err 449 } 450 if signer != "" { 451 if err := build.AzureBlobstoreUpload(archive+".asc", filepath.Base(archive+".asc"), auth); err != nil { 452 return err 453 } 454 } 455 } 456 return nil 457 } 458 459 // skips archiving for some build configurations. 460 func maybeSkipArchive(env build.Environment) { 461 if env.IsPullRequest { 462 log.Printf("skipping because this is a PR build") 463 os.Exit(0) 464 } 465 if env.IsCronJob { 466 log.Printf("skipping because this is a cron job") 467 os.Exit(0) 468 } 469 if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { 470 log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) 471 os.Exit(0) 472 } 473 } 474 475 // Debian Packaging 476 func doDebianSource(cmdline []string) { 477 var ( 478 goversion = flag.String("goversion", "", `Go version to build with (will be included in the source package)`) 479 gobundle = flag.String("gobundle", "/tmp/go.tar.gz", `Filesystem path to cache the downloaded Go bundles at`) 480 gohash = flag.String("gohash", "", `SHA256 checksum of the Go sources requested to build with`) 481 signer = flag.String("signer", "", `Signing key name, also used as package author`) 482 upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`) 483 sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`) 484 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 485 now = time.Now() 486 ) 487 flag.CommandLine.Parse(cmdline) 488 *workdir = makeWorkdir(*workdir) 489 env := build.Env() 490 maybeSkipArchive(env) 491 492 // Import the signing key. 493 if key := getenvBase64("PPA_SIGNING_KEY"); len(key) > 0 { 494 gpg := exec.Command("gpg", "--import") 495 gpg.Stdin = bytes.NewReader(key) 496 build.MustRun(gpg) 497 } 498 // Download and verify the Go source package 499 if err := build.EnsureGoSources(*goversion, hexutil.MustDecode("0x"+*gohash), *gobundle); err != nil { 500 log.Fatalf("Failed to ensure Go source package: %v", err) 501 } 502 // Create Debian packages and upload them 503 for _, pkg := range debPackages { 504 for distro, goboot := range debDistroGoBoots { 505 // Prepare the debian package with the go-ethereum sources 506 meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) 507 pkgdir := stageDebianSource(*workdir, meta) 508 509 // Ship the Go sources along so we have a proper thing to build with 510 if err := build.ExtractTarballArchive(*gobundle, pkgdir); err != nil { 511 log.Fatalf("Failed to extract Go sources: %v", err) 512 } 513 if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { 514 log.Fatalf("Failed to rename Go source folder: %v", err) 515 } 516 // Run the packaging and upload to the PPA 517 debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz") 518 debuild.Dir = pkgdir 519 build.MustRun(debuild) 520 521 var ( 522 basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) 523 source = filepath.Join(*workdir, basename+".tar.xz") 524 dsc = filepath.Join(*workdir, basename+".dsc") 525 changes = filepath.Join(*workdir, basename+"_source.changes") 526 ) 527 if *signer != "" { 528 build.MustRunCommand("debsign", changes) 529 } 530 if *upload != "" { 531 ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes}) 532 } 533 } 534 } 535 } 536 537 func ppaUpload(workdir, ppa, sshUser string, files []string) { 538 p := strings.Split(ppa, "/") 539 if len(p) != 2 { 540 log.Fatal("-upload PPA name must contain single /") 541 } 542 if sshUser == "" { 543 sshUser = p[0] 544 } 545 incomingDir := fmt.Sprintf("~%s/ubuntu/%s", p[0], p[1]) 546 // Create the SSH identity file if it doesn't exist. 547 var idfile string 548 if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 { 549 idfile = filepath.Join(workdir, "sshkey") 550 if _, err := os.Stat(idfile); os.IsNotExist(err) { 551 ioutil.WriteFile(idfile, sshkey, 0600) 552 } 553 } 554 // Upload 555 dest := sshUser + "@ppa.launchpad.net" 556 if err := build.UploadSFTP(idfile, dest, incomingDir, files); err != nil { 557 log.Fatal(err) 558 } 559 } 560 561 func getenvBase64(variable string) []byte { 562 dec, err := base64.StdEncoding.DecodeString(os.Getenv(variable)) 563 if err != nil { 564 log.Fatal("invalid base64 " + variable) 565 } 566 return []byte(dec) 567 } 568 569 func makeWorkdir(wdflag string) string { 570 var err error 571 if wdflag != "" { 572 err = os.MkdirAll(wdflag, 0744) 573 } else { 574 wdflag, err = ioutil.TempDir("", "geth-build-") 575 } 576 if err != nil { 577 log.Fatal(err) 578 } 579 return wdflag 580 } 581 582 func isUnstableBuild(env build.Environment) bool { 583 if env.Tag != "" { 584 return false 585 } 586 return true 587 } 588 589 type debPackage struct { 590 Name string // the name of the Debian package to produce, e.g. "ethereum" 591 Version string // the clean version of the debPackage, e.g. 1.8.12, without any metadata 592 Executables []debExecutable // executables to be included in the package 593 } 594 595 type debMetadata struct { 596 Env build.Environment 597 GoBootPackage string 598 GoBootPath string 599 600 PackageName string 601 602 // go-ethereum version being built. Note that this 603 // is not the debian package version. The package version 604 // is constructed by VersionString. 605 Version string 606 607 Author string // "name <email>", also selects signing key 608 Distro, Time string 609 Executables []debExecutable 610 } 611 612 type debExecutable struct { 613 PackageName string 614 BinaryName string 615 Description string 616 } 617 618 // Package returns the name of the package if present, or 619 // fallbacks to BinaryName 620 func (d debExecutable) Package() string { 621 if d.PackageName != "" { 622 return d.PackageName 623 } 624 return d.BinaryName 625 } 626 627 func newDebMetadata(distro, goboot, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata { 628 if author == "" { 629 // No signing key, use default author. 630 author = "Ethereum Builds <fjl@ethereum.org>" 631 } 632 return debMetadata{ 633 GoBootPackage: goboot, 634 GoBootPath: debGoBootPaths[goboot], 635 PackageName: name, 636 Env: env, 637 Author: author, 638 Distro: distro, 639 Version: version, 640 Time: t.Format(time.RFC1123Z), 641 Executables: exes, 642 } 643 } 644 645 // Name returns the name of the metapackage that depends 646 // on all executable packages. 647 func (meta debMetadata) Name() string { 648 if isUnstableBuild(meta.Env) { 649 return meta.PackageName + "-unstable" 650 } 651 return meta.PackageName 652 } 653 654 // VersionString returns the debian version of the packages. 655 func (meta debMetadata) VersionString() string { 656 vsn := meta.Version 657 if meta.Env.Buildnum != "" { 658 vsn += "+build" + meta.Env.Buildnum 659 } 660 if meta.Distro != "" { 661 vsn += "+" + meta.Distro 662 } 663 return vsn 664 } 665 666 // ExeList returns the list of all executable packages. 667 func (meta debMetadata) ExeList() string { 668 names := make([]string, len(meta.Executables)) 669 for i, e := range meta.Executables { 670 names[i] = meta.ExeName(e) 671 } 672 return strings.Join(names, ", ") 673 } 674 675 // ExeName returns the package name of an executable package. 676 func (meta debMetadata) ExeName(exe debExecutable) string { 677 if isUnstableBuild(meta.Env) { 678 return exe.Package() + "-unstable" 679 } 680 return exe.Package() 681 } 682 683 // ExeConflicts returns the content of the Conflicts field 684 // for executable packages. 685 func (meta debMetadata) ExeConflicts(exe debExecutable) string { 686 if isUnstableBuild(meta.Env) { 687 // Set up the conflicts list so that the *-unstable packages 688 // cannot be installed alongside the regular version. 689 // 690 // https://www.debian.org/doc/debian-policy/ch-relationships.html 691 // is very explicit about Conflicts: and says that Breaks: should 692 // be preferred and the conflicting files should be handled via 693 // alternates. We might do this eventually but using a conflict is 694 // easier now. 695 return "ethereum, " + exe.Package() 696 } 697 return "" 698 } 699 700 func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { 701 pkg := meta.Name() + "-" + meta.VersionString() 702 pkgdir = filepath.Join(tmpdir, pkg) 703 if err := os.Mkdir(pkgdir, 0755); err != nil { 704 log.Fatal(err) 705 } 706 // Copy the source code. 707 build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator)) 708 709 // Put the debian build files in place. 710 debian := filepath.Join(pkgdir, "debian") 711 build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta) 712 build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta) 713 build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta) 714 build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta) 715 build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta) 716 build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta) 717 for _, exe := range meta.Executables { 718 install := filepath.Join(debian, meta.ExeName(exe)+".install") 719 docs := filepath.Join(debian, meta.ExeName(exe)+".docs") 720 build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe) 721 build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe) 722 } 723 return pkgdir 724 } 725 726 // Windows installer 727 func doWindowsInstaller(cmdline []string) { 728 // Parse the flags and make skip installer generation on PRs 729 var ( 730 arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") 731 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) 732 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 733 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 734 ) 735 flag.CommandLine.Parse(cmdline) 736 *workdir = makeWorkdir(*workdir) 737 env := build.Env() 738 maybeSkipArchive(env) 739 740 // Aggregate binaries that are included in the installer 741 var ( 742 devTools []string 743 allTools []string 744 gethTool string 745 ) 746 for _, file := range allToolsArchiveFiles { 747 if file == "COPYING" { // license, copied later 748 continue 749 } 750 allTools = append(allTools, filepath.Base(file)) 751 if filepath.Base(file) == "geth.exe" { 752 gethTool = file 753 } else { 754 devTools = append(devTools, file) 755 } 756 } 757 758 // Render NSIS scripts: Installer NSIS contains two installer sections, 759 // first section contains the geth binary, second section holds the dev tools. 760 templateData := map[string]interface{}{ 761 "License": "COPYING", 762 "Geth": gethTool, 763 "DevTools": devTools, 764 } 765 build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil) 766 build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData) 767 build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools) 768 build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil) 769 build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil) 770 build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0755) 771 build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0755) 772 773 // Build the installer. This assumes that all the needed files have been previously 774 // built (don't mix building and packaging to keep cross compilation complexity to a 775 // minimum). 776 version := strings.Split(params.Version, ".") 777 if env.Commit != "" { 778 version[2] += "-" + env.Commit[:8] 779 } 780 installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe") 781 build.MustRunCommand("makensis.exe", 782 "/DOUTPUTFILE="+installer, 783 "/DMAJORVERSION="+version[0], 784 "/DMINORVERSION="+version[1], 785 "/DBUILDVERSION="+version[2], 786 "/DARCH="+*arch, 787 filepath.Join(*workdir, "geth.nsi"), 788 ) 789 790 // Sign and publish installer. 791 if err := archiveUpload(installer, *upload, *signer); err != nil { 792 log.Fatal(err) 793 } 794 } 795 796 // Android archives 797 798 func doAndroidArchive(cmdline []string) { 799 var ( 800 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 801 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) 802 deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) 803 upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) 804 ) 805 flag.CommandLine.Parse(cmdline) 806 env := build.Env() 807 808 // Sanity check that the SDK and NDK are installed and set 809 if os.Getenv("ANDROID_HOME") == "" { 810 log.Fatal("Please ensure ANDROID_HOME points to your Android SDK") 811 } 812 // Build the Android archive and Maven resources 813 build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) 814 build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile")) 815 816 if *local { 817 // If we're building locally, copy bundle to build dir and skip Maven 818 os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar")) 819 return 820 } 821 meta := newMavenMetadata(env) 822 build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta) 823 824 // Skip Maven deploy and Azure upload for PR builds 825 maybeSkipArchive(env) 826 827 // Sign and upload the archive to Azure 828 archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar" 829 os.Rename("geth.aar", archive) 830 831 if err := archiveUpload(archive, *upload, *signer); err != nil { 832 log.Fatal(err) 833 } 834 // Sign and upload all the artifacts to Maven Central 835 os.Rename(archive, meta.Package+".aar") 836 if *signer != "" && *deploy != "" { 837 // Import the signing key into the local GPG instance 838 key := getenvBase64(*signer) 839 gpg := exec.Command("gpg", "--import") 840 gpg.Stdin = bytes.NewReader(key) 841 build.MustRun(gpg) 842 keyID, err := build.PGPKeyID(string(key)) 843 if err != nil { 844 log.Fatal(err) 845 } 846 // Upload the artifacts to Sonatype and/or Maven Central 847 repo := *deploy + "/service/local/staging/deploy/maven2" 848 if meta.Develop { 849 repo = *deploy + "/content/repositories/snapshots" 850 } 851 build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X", 852 "-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh", 853 "-Dgpg.keyname="+keyID, 854 "-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar") 855 } 856 } 857 858 func gomobileTool(subcmd string, args ...string) *exec.Cmd { 859 cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd) 860 cmd.Args = append(cmd.Args, args...) 861 cmd.Env = []string{ 862 "GOPATH=" + build.GOPATH(), 863 "PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"), 864 } 865 for _, e := range os.Environ() { 866 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") { 867 continue 868 } 869 cmd.Env = append(cmd.Env, e) 870 } 871 return cmd 872 } 873 874 type mavenMetadata struct { 875 Version string 876 Package string 877 Develop bool 878 Contributors []mavenContributor 879 } 880 881 type mavenContributor struct { 882 Name string 883 Email string 884 } 885 886 func newMavenMetadata(env build.Environment) mavenMetadata { 887 // Collect the list of authors from the repo root 888 contribs := []mavenContributor{} 889 if authors, err := os.Open("AUTHORS"); err == nil { 890 defer authors.Close() 891 892 scanner := bufio.NewScanner(authors) 893 for scanner.Scan() { 894 // Skip any whitespace from the authors list 895 line := strings.TrimSpace(scanner.Text()) 896 if line == "" || line[0] == '#' { 897 continue 898 } 899 // Split the author and insert as a contributor 900 re := regexp.MustCompile("([^<]+) <(.+)>") 901 parts := re.FindStringSubmatch(line) 902 if len(parts) == 3 { 903 contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]}) 904 } 905 } 906 } 907 // Render the version and package strings 908 version := params.Version 909 if isUnstableBuild(env) { 910 version += "-SNAPSHOT" 911 } 912 return mavenMetadata{ 913 Version: version, 914 Package: "geth-" + version, 915 Develop: isUnstableBuild(env), 916 Contributors: contribs, 917 } 918 } 919 920 // XCode frameworks 921 922 func doXCodeFramework(cmdline []string) { 923 var ( 924 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 925 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) 926 deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) 927 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 928 ) 929 flag.CommandLine.Parse(cmdline) 930 env := build.Env() 931 932 // Build the iOS XCode framework 933 build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) 934 build.MustRun(gomobileTool("init")) 935 bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/ethereum/go-ethereum/mobile") 936 937 if *local { 938 // If we're building locally, use the build folder and stop afterwards 939 bind.Dir, _ = filepath.Abs(GOBIN) 940 build.MustRun(bind) 941 return 942 } 943 archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit)) 944 if err := os.Mkdir(archive, os.ModePerm); err != nil { 945 log.Fatal(err) 946 } 947 bind.Dir, _ = filepath.Abs(archive) 948 build.MustRun(bind) 949 build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) 950 951 // Skip CocoaPods deploy and Azure upload for PR builds 952 maybeSkipArchive(env) 953 954 // Sign and upload the framework to Azure 955 if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil { 956 log.Fatal(err) 957 } 958 // Prepare and upload a PodSpec to CocoaPods 959 if *deploy != "" { 960 meta := newPodMetadata(env, archive) 961 build.Render("build/pod.podspec", "Geth.podspec", 0755, meta) 962 build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings", "--verbose") 963 } 964 } 965 966 type podMetadata struct { 967 Version string 968 Commit string 969 Archive string 970 Contributors []podContributor 971 } 972 973 type podContributor struct { 974 Name string 975 Email string 976 } 977 978 func newPodMetadata(env build.Environment, archive string) podMetadata { 979 // Collect the list of authors from the repo root 980 contribs := []podContributor{} 981 if authors, err := os.Open("AUTHORS"); err == nil { 982 defer authors.Close() 983 984 scanner := bufio.NewScanner(authors) 985 for scanner.Scan() { 986 // Skip any whitespace from the authors list 987 line := strings.TrimSpace(scanner.Text()) 988 if line == "" || line[0] == '#' { 989 continue 990 } 991 // Split the author and insert as a contributor 992 re := regexp.MustCompile("([^<]+) <(.+)>") 993 parts := re.FindStringSubmatch(line) 994 if len(parts) == 3 { 995 contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]}) 996 } 997 } 998 } 999 version := params.Version 1000 if isUnstableBuild(env) { 1001 version += "-unstable." + env.Buildnum 1002 } 1003 return podMetadata{ 1004 Archive: archive, 1005 Version: version, 1006 Commit: env.Commit, 1007 Contributors: contribs, 1008 } 1009 } 1010 1011 // Cross compilation 1012 1013 func doXgo(cmdline []string) { 1014 var ( 1015 alltools = flag.Bool("alltools", false, `Flag whether we're building all known tools, or only on in particular`) 1016 ) 1017 flag.CommandLine.Parse(cmdline) 1018 env := build.Env() 1019 1020 // Make sure xgo is available for cross compilation 1021 gogetxgo := goTool("get", "github.com/karalabe/xgo") 1022 build.MustRun(gogetxgo) 1023 1024 // If all tools building is requested, build everything the builder wants 1025 args := append(buildFlags(env), flag.Args()...) 1026 1027 if *alltools { 1028 args = append(args, []string{"--dest", GOBIN}...) 1029 for _, res := range allToolsArchiveFiles { 1030 if strings.HasPrefix(res, GOBIN) { 1031 // Binary tool found, cross build it explicitly 1032 args = append(args, "./"+filepath.Join("cmd", filepath.Base(res))) 1033 xgo := xgoTool(args) 1034 build.MustRun(xgo) 1035 args = args[:len(args)-1] 1036 } 1037 } 1038 return 1039 } 1040 // Otherwise xxecute the explicit cross compilation 1041 path := args[len(args)-1] 1042 args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...) 1043 1044 xgo := xgoTool(args) 1045 build.MustRun(xgo) 1046 } 1047 1048 func xgoTool(args []string) *exec.Cmd { 1049 cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...) 1050 cmd.Env = []string{ 1051 "GOPATH=" + build.GOPATH(), 1052 "GOBIN=" + GOBIN, 1053 } 1054 for _, e := range os.Environ() { 1055 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") { 1056 continue 1057 } 1058 cmd.Env = append(cmd.Env, e) 1059 } 1060 return cmd 1061 } 1062 1063 // Binary distribution cleanups 1064 1065 func doPurge(cmdline []string) { 1066 var ( 1067 store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`) 1068 limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`) 1069 ) 1070 flag.CommandLine.Parse(cmdline) 1071 1072 if env := build.Env(); !env.IsCronJob { 1073 log.Printf("skipping because not a cron job") 1074 os.Exit(0) 1075 } 1076 // Create the azure authentication and list the current archives 1077 auth := build.AzureBlobstoreConfig{ 1078 Account: strings.Split(*store, "/")[0], 1079 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 1080 Container: strings.SplitN(*store, "/", 2)[1], 1081 } 1082 blobs, err := build.AzureBlobstoreList(auth) 1083 if err != nil { 1084 log.Fatal(err) 1085 } 1086 // Iterate over the blobs, collect and sort all unstable builds 1087 for i := 0; i < len(blobs); i++ { 1088 if !strings.Contains(blobs[i].Name, "unstable") { 1089 blobs = append(blobs[:i], blobs[i+1:]...) 1090 i-- 1091 } 1092 } 1093 for i := 0; i < len(blobs); i++ { 1094 for j := i + 1; j < len(blobs); j++ { 1095 if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) { 1096 blobs[i], blobs[j] = blobs[j], blobs[i] 1097 } 1098 } 1099 } 1100 // Filter out all archives more recent that the given threshold 1101 for i, blob := range blobs { 1102 if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour { 1103 blobs = blobs[:i] 1104 break 1105 } 1106 } 1107 // Delete all marked as such and return 1108 if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { 1109 log.Fatal(err) 1110 } 1111 }