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