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