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