github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/build/ci.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 //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 ] [ -signify 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 purge [ -store blobstore ] [ -days threshold ] -- purges old archives from the blobstore 35 36 For all commands, -n prevents execution of external programs (dry run mode). 37 */ 38 package main 39 40 import ( 41 "bytes" 42 "encoding/base64" 43 "flag" 44 "fmt" 45 "log" 46 "os" 47 "os/exec" 48 "path" 49 "path/filepath" 50 "runtime" 51 "strconv" 52 "strings" 53 "time" 54 55 "github.com/cespare/cp" 56 "github.com/ethereum/go-ethereum/common" 57 "github.com/ethereum/go-ethereum/crypto/signify" 58 "github.com/ethereum/go-ethereum/internal/build" 59 "github.com/ethereum/go-ethereum/params" 60 ) 61 62 var ( 63 // Files that end up in the geth*.zip archive. 64 gethArchiveFiles = []string{ 65 "COPYING", 66 executablePath("geth"), 67 } 68 69 // Files that end up in the geth-alltools*.zip archive. 70 allToolsArchiveFiles = []string{ 71 "COPYING", 72 executablePath("abigen"), 73 executablePath("bootnode"), 74 executablePath("evm"), 75 executablePath("geth"), 76 executablePath("rlpdump"), 77 executablePath("clef"), 78 } 79 80 // A debian package is created for all executables listed here. 81 debExecutables = []debExecutable{ 82 { 83 BinaryName: "abigen", 84 Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.", 85 }, 86 { 87 BinaryName: "bootnode", 88 Description: "Ethereum bootnode.", 89 }, 90 { 91 BinaryName: "evm", 92 Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.", 93 }, 94 { 95 BinaryName: "geth", 96 Description: "Ethereum CLI client.", 97 }, 98 { 99 BinaryName: "rlpdump", 100 Description: "Developer utility tool that prints RLP structures.", 101 }, 102 { 103 BinaryName: "clef", 104 Description: "Ethereum account management tool.", 105 }, 106 } 107 108 // A debian package is created for all executables listed here. 109 debEthereum = debPackage{ 110 Name: "ethereum", 111 Version: params.Version, 112 Executables: debExecutables, 113 } 114 115 // Debian meta packages to build and push to Ubuntu PPA 116 debPackages = []debPackage{ 117 debEthereum, 118 } 119 120 // Distros for which packages are created 121 debDistros = []string{ 122 "xenial", // 16.04, EOL: 04/2026 123 "bionic", // 18.04, EOL: 04/2028 124 "focal", // 20.04, EOL: 04/2030 125 "jammy", // 22.04, EOL: 04/2032 126 "noble", // 24.04, EOL: 04/2034 127 128 "mantic", // 23.10, EOL: 07/2024 129 } 130 131 // This is where the tests should be unpacked. 132 executionSpecTestsDir = "tests/spec-tests" 133 ) 134 135 var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) 136 137 func executablePath(name string) string { 138 if runtime.GOOS == "windows" { 139 name += ".exe" 140 } 141 return filepath.Join(GOBIN, name) 142 } 143 144 func main() { 145 log.SetFlags(log.Lshortfile) 146 147 if !common.FileExist(filepath.Join("build", "ci.go")) { 148 log.Fatal("this script must be run from the root of the repository") 149 } 150 if len(os.Args) < 2 { 151 log.Fatal("need subcommand as first argument") 152 } 153 switch os.Args[1] { 154 case "install": 155 doInstall(os.Args[2:]) 156 case "test": 157 doTest(os.Args[2:]) 158 case "lint": 159 doLint(os.Args[2:]) 160 case "archive": 161 doArchive(os.Args[2:]) 162 case "docker": 163 doDocker(os.Args[2:]) 164 case "debsrc": 165 doDebianSource(os.Args[2:]) 166 case "nsis": 167 doWindowsInstaller(os.Args[2:]) 168 case "purge": 169 doPurge(os.Args[2:]) 170 case "sanitycheck": 171 doSanityCheck() 172 default: 173 log.Fatal("unknown command ", os.Args[1]) 174 } 175 } 176 177 // Compiling 178 179 func doInstall(cmdline []string) { 180 var ( 181 dlgo = flag.Bool("dlgo", false, "Download Go and build with it") 182 arch = flag.String("arch", "", "Architecture to cross build for") 183 cc = flag.String("cc", "", "C compiler to cross build with") 184 staticlink = flag.Bool("static", false, "Create statically-linked executable") 185 ) 186 flag.CommandLine.Parse(cmdline) 187 env := build.Env() 188 189 // Configure the toolchain. 190 tc := build.GoToolchain{GOARCH: *arch, CC: *cc} 191 if *dlgo { 192 csdb := build.MustLoadChecksums("build/checksums.txt") 193 tc.Root = build.DownloadGo(csdb) 194 } 195 // Disable CLI markdown doc generation in release builds. 196 buildTags := []string{"urfave_cli_no_docs"} 197 198 // Enable linking the CKZG library since we can make it work with additional flags. 199 if env.UbuntuVersion != "trusty" { 200 buildTags = append(buildTags, "ckzg") 201 } 202 203 // Configure the build. 204 gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...) 205 206 // arm64 CI builders are memory-constrained and can't handle concurrent builds, 207 // better disable it. This check isn't the best, it should probably 208 // check for something in env instead. 209 if env.CI && runtime.GOARCH == "arm64" { 210 gobuild.Args = append(gobuild.Args, "-p", "1") 211 } 212 // We use -trimpath to avoid leaking local paths into the built executables. 213 gobuild.Args = append(gobuild.Args, "-trimpath") 214 215 // Show packages during build. 216 gobuild.Args = append(gobuild.Args, "-v") 217 218 // Now we choose what we're even building. 219 // Default: collect all 'main' packages in cmd/ and build those. 220 packages := flag.Args() 221 if len(packages) == 0 { 222 packages = build.FindMainPackages("./cmd") 223 } 224 225 // Do the build! 226 for _, pkg := range packages { 227 args := make([]string, len(gobuild.Args)) 228 copy(args, gobuild.Args) 229 args = append(args, "-o", executablePath(path.Base(pkg))) 230 args = append(args, pkg) 231 build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) 232 } 233 } 234 235 // buildFlags returns the go tool flags for building. 236 func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) { 237 var ld []string 238 if env.Commit != "" { 239 ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitCommit="+env.Commit) 240 ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitDate="+env.Date) 241 } 242 // Strip DWARF on darwin. This used to be required for certain things, 243 // and there is no downside to this, so we just keep doing it. 244 if runtime.GOOS == "darwin" { 245 ld = append(ld, "-s") 246 } 247 if runtime.GOOS == "linux" { 248 // Enforce the stacksize to 8M, which is the case on most platforms apart from 249 // alpine Linux. 250 extld := []string{"-Wl,-z,stack-size=0x800000"} 251 if staticLinking { 252 extld = append(extld, "-static") 253 // Under static linking, use of certain glibc features must be 254 // disabled to avoid shared library dependencies. 255 buildTags = append(buildTags, "osusergo", "netgo") 256 } 257 ld = append(ld, "-extldflags", "'"+strings.Join(extld, " ")+"'") 258 } 259 if len(ld) > 0 { 260 flags = append(flags, "-ldflags", strings.Join(ld, " ")) 261 } 262 if len(buildTags) > 0 { 263 flags = append(flags, "-tags", strings.Join(buildTags, ",")) 264 } 265 return flags 266 } 267 268 // Running The Tests 269 // 270 // "tests" also includes static analysis tools such as vet. 271 272 func doTest(cmdline []string) { 273 var ( 274 dlgo = flag.Bool("dlgo", false, "Download Go and build with it") 275 arch = flag.String("arch", "", "Run tests for given architecture") 276 cc = flag.String("cc", "", "Sets C compiler binary") 277 coverage = flag.Bool("coverage", false, "Whether to record code coverage") 278 verbose = flag.Bool("v", false, "Whether to log verbosely") 279 race = flag.Bool("race", false, "Execute the race detector") 280 short = flag.Bool("short", false, "Pass the 'short'-flag to go test") 281 cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") 282 ) 283 flag.CommandLine.Parse(cmdline) 284 285 // Get test fixtures. 286 csdb := build.MustLoadChecksums("build/checksums.txt") 287 downloadSpecTestFixtures(csdb, *cachedir) 288 289 // Configure the toolchain. 290 tc := build.GoToolchain{GOARCH: *arch, CC: *cc} 291 if *dlgo { 292 tc.Root = build.DownloadGo(csdb) 293 } 294 gotest := tc.Go("test") 295 296 // CI needs a bit more time for the statetests (default 10m). 297 gotest.Args = append(gotest.Args, "-timeout=20m") 298 299 // Enable CKZG backend in CI. 300 gotest.Args = append(gotest.Args, "-tags=ckzg") 301 302 // Enable integration-tests 303 gotest.Args = append(gotest.Args, "-tags=integrationtests") 304 305 // Test a single package at a time. CI builders are slow 306 // and some tests run into timeouts under load. 307 gotest.Args = append(gotest.Args, "-p", "1") 308 if *coverage { 309 gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") 310 } 311 if *verbose { 312 gotest.Args = append(gotest.Args, "-v") 313 } 314 if *race { 315 gotest.Args = append(gotest.Args, "-race") 316 } 317 if *short { 318 gotest.Args = append(gotest.Args, "-short") 319 } 320 321 packages := []string{"./..."} 322 if len(flag.CommandLine.Args()) > 0 { 323 packages = flag.CommandLine.Args() 324 } 325 gotest.Args = append(gotest.Args, packages...) 326 build.MustRun(gotest) 327 } 328 329 // downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. 330 func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string { 331 executionSpecTestsVersion, err := build.Version(csdb, "spec-tests") 332 if err != nil { 333 log.Fatal(err) 334 } 335 ext := ".tar.gz" 336 base := "fixtures_develop" // TODO(MariusVanDerWijden) rename once the version becomes part of the filename 337 url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/v%s/%s%s", executionSpecTestsVersion, base, ext) 338 archivePath := filepath.Join(cachedir, base+ext) 339 if err := csdb.DownloadFile(url, archivePath); err != nil { 340 log.Fatal(err) 341 } 342 if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil { 343 log.Fatal(err) 344 } 345 return filepath.Join(cachedir, base) 346 } 347 348 // doLint runs golangci-lint on requested packages. 349 func doLint(cmdline []string) { 350 var ( 351 cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.") 352 ) 353 flag.CommandLine.Parse(cmdline) 354 packages := []string{"./..."} 355 if len(flag.CommandLine.Args()) > 0 { 356 packages = flag.CommandLine.Args() 357 } 358 359 linter := downloadLinter(*cachedir) 360 lflags := []string{"run", "--config", ".golangci.yml"} 361 build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) 362 fmt.Println("You have achieved perfection.") 363 } 364 365 // downloadLinter downloads and unpacks golangci-lint. 366 func downloadLinter(cachedir string) string { 367 csdb := build.MustLoadChecksums("build/checksums.txt") 368 version, err := build.Version(csdb, "golangci") 369 if err != nil { 370 log.Fatal(err) 371 } 372 arch := runtime.GOARCH 373 ext := ".tar.gz" 374 375 if runtime.GOOS == "windows" { 376 ext = ".zip" 377 } 378 if arch == "arm" { 379 arch += "v" + os.Getenv("GOARM") 380 } 381 base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch) 382 url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s%s", version, base, ext) 383 archivePath := filepath.Join(cachedir, base+ext) 384 if err := csdb.DownloadFile(url, archivePath); err != nil { 385 log.Fatal(err) 386 } 387 if err := build.ExtractArchive(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 signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`) 400 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 401 ext string 402 ) 403 flag.CommandLine.Parse(cmdline) 404 switch *atype { 405 case "zip": 406 ext = ".zip" 407 case "tar": 408 ext = ".tar.gz" 409 default: 410 log.Fatal("unknown archive type: ", atype) 411 } 412 413 var ( 414 env = build.Env() 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, *signify); 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, signifyVar 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 signifyVar != "" { 456 key := os.Getenv(signifyVar) 457 untrustedComment := "verify with geth-release.pub" 458 trustedComment := fmt.Sprintf("%s (%s)", archive, time.Now().UTC().Format(time.RFC1123)) 459 if err := signify.SignFile(archive, archive+".sig", key, untrustedComment, trustedComment); err != nil { 460 return err 461 } 462 } 463 // If uploading to Azure was requested, push the archive possibly with its signature 464 if blobstore != "" { 465 auth := build.AzureBlobstoreConfig{ 466 Account: strings.Split(blobstore, "/")[0], 467 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 468 Container: strings.SplitN(blobstore, "/", 2)[1], 469 } 470 if err := build.AzureBlobstoreUpload(archive, filepath.Base(archive), auth); err != nil { 471 return err 472 } 473 if signer != "" { 474 if err := build.AzureBlobstoreUpload(archive+".asc", filepath.Base(archive+".asc"), auth); err != nil { 475 return err 476 } 477 } 478 if signifyVar != "" { 479 if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { 480 return err 481 } 482 } 483 } 484 return nil 485 } 486 487 // skips archiving for some build configurations. 488 func maybeSkipArchive(env build.Environment) { 489 if env.IsPullRequest { 490 log.Printf("skipping archive creation because this is a PR build") 491 os.Exit(0) 492 } 493 if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { 494 log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag) 495 os.Exit(0) 496 } 497 } 498 499 // Builds the docker images and optionally uploads them to Docker Hub. 500 func doDocker(cmdline []string) { 501 var ( 502 image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`) 503 manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`) 504 upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) 505 ) 506 flag.CommandLine.Parse(cmdline) 507 508 // Skip building and pushing docker images for PR builds 509 env := build.Env() 510 maybeSkipArchive(env) 511 512 // Retrieve the upload credentials and authenticate 513 user := getenvBase64("DOCKER_HUB_USERNAME") 514 pass := getenvBase64("DOCKER_HUB_PASSWORD") 515 516 if len(user) > 0 && len(pass) > 0 { 517 auther := exec.Command("docker", "login", "-u", string(user), "--password-stdin") 518 auther.Stdin = bytes.NewReader(pass) 519 build.MustRun(auther) 520 } 521 // Retrieve the version infos to build and push to the following paths: 522 // - ethereum/client-go:latest - Pushes to the master branch, Geth only 523 // - ethereum/client-go:stable - Version tag publish on GitHub, Geth only 524 // - ethereum/client-go:alltools-latest - Pushes to the master branch, Geth & tools 525 // - ethereum/client-go:alltools-stable - Version tag publish on GitHub, Geth & tools 526 // - ethereum/client-go:release-<major>.<minor> - Version tag publish on GitHub, Geth only 527 // - ethereum/client-go:alltools-release-<major>.<minor> - Version tag publish on GitHub, Geth & tools 528 // - ethereum/client-go:v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth only 529 // - ethereum/client-go:alltools-v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth & tools 530 var tags []string 531 532 switch { 533 case env.Branch == "master": 534 tags = []string{"latest"} 535 case strings.HasPrefix(env.Tag, "v1."): 536 tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version} 537 } 538 // If architecture specific image builds are requested, build and push them 539 if *image { 540 build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:TAG", *upload), ".") 541 build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".") 542 543 // Tag and upload the images to Docker Hub 544 for _, tag := range tags { 545 gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH) 546 toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH) 547 548 // If the image already exists (non version tag), check the build 549 // number to prevent overwriting a newer commit if concurrent builds 550 // are running. This is still a tiny bit racey if two published are 551 // done at the same time, but that's extremely unlikely even on the 552 // master branch. 553 for _, img := range []string{gethImage, toolImage} { 554 if exec.Command("docker", "pull", img).Run() != nil { 555 continue // Generally the only failure is a missing image, which is good 556 } 557 buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() 558 if err != nil { 559 log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) 560 } 561 buildnum = bytes.TrimSpace(buildnum) 562 563 if len(buildnum) > 0 && len(env.Buildnum) > 0 { 564 oldnum, err := strconv.Atoi(string(buildnum)) 565 if err != nil { 566 log.Fatalf("Failed to parse old image build number: %v", err) 567 } 568 newnum, err := strconv.Atoi(env.Buildnum) 569 if err != nil { 570 log.Fatalf("Failed to parse current build number: %v", err) 571 } 572 if oldnum > newnum { 573 log.Fatalf("Current build number %d not newer than existing %d", newnum, oldnum) 574 } else { 575 log.Printf("Updating %s from build %d to %d", img, oldnum, newnum) 576 } 577 } 578 } 579 build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), gethImage) 580 build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), toolImage) 581 build.MustRunCommand("docker", "push", gethImage) 582 build.MustRunCommand("docker", "push", toolImage) 583 } 584 } 585 // If multi-arch image manifest push is requested, assemble it 586 if len(*manifest) != 0 { 587 // Since different architectures are pushed by different builders, wait 588 // until all required images are updated. 589 var mismatch bool 590 for i := 0; i < 2; i++ { // 2 attempts, second is race check 591 mismatch = false // hope there's no mismatch now 592 593 for _, tag := range tags { 594 for _, arch := range strings.Split(*manifest, ",") { 595 gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, arch) 596 toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, arch) 597 598 for _, img := range []string{gethImage, toolImage} { 599 if out, err := exec.Command("docker", "pull", img).CombinedOutput(); err != nil { 600 log.Printf("Required image %s unavailable: %v\nOutput: %s", img, err, out) 601 mismatch = true 602 break 603 } 604 buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() 605 if err != nil { 606 log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) 607 } 608 buildnum = bytes.TrimSpace(buildnum) 609 610 if string(buildnum) != env.Buildnum { 611 log.Printf("Build number mismatch on %s: want %s, have %s", img, env.Buildnum, buildnum) 612 mismatch = true 613 break 614 } 615 } 616 if mismatch { 617 break 618 } 619 } 620 if mismatch { 621 break 622 } 623 } 624 if mismatch { 625 // Build numbers mismatching, retry in a short time to 626 // avoid concurrent fails in both publisher images. If 627 // however the retry failed too, it means the concurrent 628 // builder is still crunching, let that do the publish. 629 if i == 0 { 630 time.Sleep(30 * time.Second) 631 } 632 continue 633 } 634 break 635 } 636 if mismatch { 637 log.Println("Relinquishing publish to other builder") 638 return 639 } 640 // Assemble and push the Geth manifest image 641 for _, tag := range tags { 642 gethImage := fmt.Sprintf("%s:%s", *upload, tag) 643 644 var gethSubImages []string 645 for _, arch := range strings.Split(*manifest, ",") { 646 gethSubImages = append(gethSubImages, gethImage+"-"+arch) 647 } 648 build.MustRunCommand("docker", append([]string{"manifest", "create", gethImage}, gethSubImages...)...) 649 build.MustRunCommand("docker", "manifest", "push", gethImage) 650 } 651 // Assemble and push the alltools manifest image 652 for _, tag := range tags { 653 toolImage := fmt.Sprintf("%s:alltools-%s", *upload, tag) 654 655 var toolSubImages []string 656 for _, arch := range strings.Split(*manifest, ",") { 657 toolSubImages = append(toolSubImages, toolImage+"-"+arch) 658 } 659 build.MustRunCommand("docker", append([]string{"manifest", "create", toolImage}, toolSubImages...)...) 660 build.MustRunCommand("docker", "manifest", "push", toolImage) 661 } 662 } 663 } 664 665 // Debian Packaging 666 func doDebianSource(cmdline []string) { 667 var ( 668 cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`) 669 signer = flag.String("signer", "", `Signing key name, also used as package author`) 670 upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`) 671 sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`) 672 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 673 now = time.Now() 674 ) 675 flag.CommandLine.Parse(cmdline) 676 *workdir = makeWorkdir(*workdir) 677 env := build.Env() 678 tc := new(build.GoToolchain) 679 maybeSkipArchive(env) 680 681 // Import the signing key. 682 if key := getenvBase64("PPA_SIGNING_KEY"); len(key) > 0 { 683 gpg := exec.Command("gpg", "--import") 684 gpg.Stdin = bytes.NewReader(key) 685 build.MustRun(gpg) 686 } 687 // Download and verify the Go source packages. 688 var ( 689 gobootbundles = downloadGoBootstrapSources(*cachedir) 690 gobundle = downloadGoSources(*cachedir) 691 ) 692 // Download all the dependencies needed to build the sources and run the ci script 693 srcdepfetch := tc.Go("mod", "download") 694 srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) 695 build.MustRun(srcdepfetch) 696 697 cidepfetch := tc.Go("run", "./build/ci.go") 698 cidepfetch.Env = append(cidepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) 699 cidepfetch.Run() // Command fails, don't care, we only need the deps to start it 700 701 // Create Debian packages and upload them. 702 for _, pkg := range debPackages { 703 for _, distro := range debDistros { 704 // Prepare the debian package with the go-ethereum sources. 705 meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) 706 pkgdir := stageDebianSource(*workdir, meta) 707 708 // Add bootstrapper Go source code 709 for i, gobootbundle := range gobootbundles { 710 if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil { 711 log.Fatalf("Failed to extract bootstrapper Go sources: %v", err) 712 } 713 if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, fmt.Sprintf(".goboot-%d", i+1))); err != nil { 714 log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err) 715 } 716 } 717 // Add builder Go source code 718 if err := build.ExtractArchive(gobundle, pkgdir); err != nil { 719 log.Fatalf("Failed to extract builder Go sources: %v", err) 720 } 721 if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { 722 log.Fatalf("Failed to rename builder Go source folder: %v", err) 723 } 724 // Add all dependency modules in compressed form 725 os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755) 726 if err := cp.CopyAll(filepath.Join(pkgdir, ".mod", "cache", "download"), filepath.Join(*workdir, "modgopath", "pkg", "mod", "cache", "download")); err != nil { 727 log.Fatalf("Failed to copy Go module dependencies: %v", err) 728 } 729 // Run the packaging and upload to the PPA 730 debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz", "-nc") 731 debuild.Dir = pkgdir 732 build.MustRun(debuild) 733 734 var ( 735 basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) 736 source = filepath.Join(*workdir, basename+".tar.xz") 737 dsc = filepath.Join(*workdir, basename+".dsc") 738 changes = filepath.Join(*workdir, basename+"_source.changes") 739 buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo") 740 ) 741 if *signer != "" { 742 build.MustRunCommand("debsign", changes) 743 } 744 if *upload != "" { 745 ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo}) 746 } 747 } 748 } 749 } 750 751 // downloadGoBootstrapSources downloads the Go source tarball(s) that will be used 752 // to bootstrap the builder Go. 753 func downloadGoBootstrapSources(cachedir string) []string { 754 csdb := build.MustLoadChecksums("build/checksums.txt") 755 756 var bundles []string 757 for _, booter := range []string{"ppa-builder-1", "ppa-builder-2"} { 758 gobootVersion, err := build.Version(csdb, booter) 759 if err != nil { 760 log.Fatal(err) 761 } 762 file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion) 763 url := "https://dl.google.com/go/" + file 764 dst := filepath.Join(cachedir, file) 765 if err := csdb.DownloadFile(url, dst); err != nil { 766 log.Fatal(err) 767 } 768 bundles = append(bundles, dst) 769 } 770 return bundles 771 } 772 773 // downloadGoSources downloads the Go source tarball. 774 func downloadGoSources(cachedir string) string { 775 csdb := build.MustLoadChecksums("build/checksums.txt") 776 dlgoVersion, err := build.Version(csdb, "golang") 777 if err != nil { 778 log.Fatal(err) 779 } 780 file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) 781 url := "https://dl.google.com/go/" + file 782 dst := filepath.Join(cachedir, file) 783 if err := csdb.DownloadFile(url, dst); err != nil { 784 log.Fatal(err) 785 } 786 return dst 787 } 788 789 func ppaUpload(workdir, ppa, sshUser string, files []string) { 790 p := strings.Split(ppa, "/") 791 if len(p) != 2 { 792 log.Fatal("-upload PPA name must contain single /") 793 } 794 if sshUser == "" { 795 sshUser = p[0] 796 } 797 incomingDir := fmt.Sprintf("~%s/ubuntu/%s", p[0], p[1]) 798 // Create the SSH identity file if it doesn't exist. 799 var idfile string 800 if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 { 801 idfile = filepath.Join(workdir, "sshkey") 802 if !common.FileExist(idfile) { 803 os.WriteFile(idfile, sshkey, 0600) 804 } 805 } 806 // Upload 807 dest := sshUser + "@ppa.launchpad.net" 808 if err := build.UploadSFTP(idfile, dest, incomingDir, files); err != nil { 809 log.Fatal(err) 810 } 811 } 812 813 func getenvBase64(variable string) []byte { 814 dec, err := base64.StdEncoding.DecodeString(os.Getenv(variable)) 815 if err != nil { 816 log.Fatal("invalid base64 " + variable) 817 } 818 return []byte(dec) 819 } 820 821 func makeWorkdir(wdflag string) string { 822 var err error 823 if wdflag != "" { 824 err = os.MkdirAll(wdflag, 0744) 825 } else { 826 wdflag, err = os.MkdirTemp("", "geth-build-") 827 } 828 if err != nil { 829 log.Fatal(err) 830 } 831 return wdflag 832 } 833 834 func isUnstableBuild(env build.Environment) bool { 835 if env.Tag != "" { 836 return false 837 } 838 return true 839 } 840 841 type debPackage struct { 842 Name string // the name of the Debian package to produce, e.g. "ethereum" 843 Version string // the clean version of the debPackage, e.g. 1.8.12, without any metadata 844 Executables []debExecutable // executables to be included in the package 845 } 846 847 type debMetadata struct { 848 Env build.Environment 849 PackageName string 850 851 // go-ethereum version being built. Note that this 852 // is not the debian package version. The package version 853 // is constructed by VersionString. 854 Version string 855 856 Author string // "name <email>", also selects signing key 857 Distro, Time string 858 Executables []debExecutable 859 } 860 861 type debExecutable struct { 862 PackageName string 863 BinaryName string 864 Description string 865 } 866 867 // Package returns the name of the package if present, or 868 // fallbacks to BinaryName 869 func (d debExecutable) Package() string { 870 if d.PackageName != "" { 871 return d.PackageName 872 } 873 return d.BinaryName 874 } 875 876 func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata { 877 if author == "" { 878 // No signing key, use default author. 879 author = "Ethereum Builds <fjl@ethereum.org>" 880 } 881 return debMetadata{ 882 PackageName: name, 883 Env: env, 884 Author: author, 885 Distro: distro, 886 Version: version, 887 Time: t.Format(time.RFC1123Z), 888 Executables: exes, 889 } 890 } 891 892 // Name returns the name of the metapackage that depends 893 // on all executable packages. 894 func (meta debMetadata) Name() string { 895 if isUnstableBuild(meta.Env) { 896 return meta.PackageName + "-unstable" 897 } 898 return meta.PackageName 899 } 900 901 // VersionString returns the debian version of the packages. 902 func (meta debMetadata) VersionString() string { 903 vsn := meta.Version 904 if meta.Env.Buildnum != "" { 905 vsn += "+build" + meta.Env.Buildnum 906 } 907 if meta.Distro != "" { 908 vsn += "+" + meta.Distro 909 } 910 return vsn 911 } 912 913 // ExeList returns the list of all executable packages. 914 func (meta debMetadata) ExeList() string { 915 names := make([]string, len(meta.Executables)) 916 for i, e := range meta.Executables { 917 names[i] = meta.ExeName(e) 918 } 919 return strings.Join(names, ", ") 920 } 921 922 // ExeName returns the package name of an executable package. 923 func (meta debMetadata) ExeName(exe debExecutable) string { 924 if isUnstableBuild(meta.Env) { 925 return exe.Package() + "-unstable" 926 } 927 return exe.Package() 928 } 929 930 // ExeConflicts returns the content of the Conflicts field 931 // for executable packages. 932 func (meta debMetadata) ExeConflicts(exe debExecutable) string { 933 if isUnstableBuild(meta.Env) { 934 // Set up the conflicts list so that the *-unstable packages 935 // cannot be installed alongside the regular version. 936 // 937 // https://www.debian.org/doc/debian-policy/ch-relationships.html 938 // is very explicit about Conflicts: and says that Breaks: should 939 // be preferred and the conflicting files should be handled via 940 // alternates. We might do this eventually but using a conflict is 941 // easier now. 942 return "ethereum, " + exe.Package() 943 } 944 return "" 945 } 946 947 func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { 948 pkg := meta.Name() + "-" + meta.VersionString() 949 pkgdir = filepath.Join(tmpdir, pkg) 950 if err := os.Mkdir(pkgdir, 0755); err != nil { 951 log.Fatal(err) 952 } 953 // Copy the source code. 954 build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator)) 955 956 // Put the debian build files in place. 957 debian := filepath.Join(pkgdir, "debian") 958 build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta) 959 build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta) 960 build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta) 961 build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta) 962 build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta) 963 build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta) 964 for _, exe := range meta.Executables { 965 install := filepath.Join(debian, meta.ExeName(exe)+".install") 966 docs := filepath.Join(debian, meta.ExeName(exe)+".docs") 967 build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe) 968 build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe) 969 } 970 return pkgdir 971 } 972 973 // Windows installer 974 func doWindowsInstaller(cmdline []string) { 975 // Parse the flags and make skip installer generation on PRs 976 var ( 977 arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") 978 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) 979 signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) 980 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 981 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 982 ) 983 flag.CommandLine.Parse(cmdline) 984 *workdir = makeWorkdir(*workdir) 985 env := build.Env() 986 maybeSkipArchive(env) 987 988 // Aggregate binaries that are included in the installer 989 var ( 990 devTools []string 991 allTools []string 992 gethTool string 993 ) 994 for _, file := range allToolsArchiveFiles { 995 if file == "COPYING" { // license, copied later 996 continue 997 } 998 allTools = append(allTools, filepath.Base(file)) 999 if filepath.Base(file) == "geth.exe" { 1000 gethTool = file 1001 } else { 1002 devTools = append(devTools, file) 1003 } 1004 } 1005 1006 // Render NSIS scripts: Installer NSIS contains two installer sections, 1007 // first section contains the geth binary, second section holds the dev tools. 1008 templateData := map[string]interface{}{ 1009 "License": "COPYING", 1010 "Geth": gethTool, 1011 "DevTools": devTools, 1012 } 1013 build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil) 1014 build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData) 1015 build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools) 1016 build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil) 1017 build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil) 1018 if err := cp.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll"); err != nil { 1019 log.Fatalf("Failed to copy SimpleFC.dll: %v", err) 1020 } 1021 if err := cp.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING"); err != nil { 1022 log.Fatalf("Failed to copy copyright note: %v", err) 1023 } 1024 // Build the installer. This assumes that all the needed files have been previously 1025 // built (don't mix building and packaging to keep cross compilation complexity to a 1026 // minimum). 1027 version := strings.Split(params.Version, ".") 1028 if env.Commit != "" { 1029 version[2] += "-" + env.Commit[:8] 1030 } 1031 installer, err := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe") 1032 if err != nil { 1033 log.Fatalf("Failed to convert installer file path: %v", err) 1034 } 1035 build.MustRunCommand("makensis.exe", 1036 "/DOUTPUTFILE="+installer, 1037 "/DMAJORVERSION="+version[0], 1038 "/DMINORVERSION="+version[1], 1039 "/DBUILDVERSION="+version[2], 1040 "/DARCH="+*arch, 1041 filepath.Join(*workdir, "geth.nsi"), 1042 ) 1043 // Sign and publish installer. 1044 if err := archiveUpload(installer, *upload, *signer, *signify); err != nil { 1045 log.Fatal(err) 1046 } 1047 } 1048 1049 // Binary distribution cleanups 1050 1051 func doPurge(cmdline []string) { 1052 var ( 1053 store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`) 1054 limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`) 1055 ) 1056 flag.CommandLine.Parse(cmdline) 1057 1058 if env := build.Env(); !env.IsCronJob { 1059 log.Printf("skipping because not a cron job") 1060 os.Exit(0) 1061 } 1062 // Create the azure authentication and list the current archives 1063 auth := build.AzureBlobstoreConfig{ 1064 Account: strings.Split(*store, "/")[0], 1065 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 1066 Container: strings.SplitN(*store, "/", 2)[1], 1067 } 1068 blobs, err := build.AzureBlobstoreList(auth) 1069 if err != nil { 1070 log.Fatal(err) 1071 } 1072 fmt.Printf("Found %d blobs\n", len(blobs)) 1073 1074 // Iterate over the blobs, collect and sort all unstable builds 1075 for i := 0; i < len(blobs); i++ { 1076 if !strings.Contains(*blobs[i].Name, "unstable") { 1077 blobs = append(blobs[:i], blobs[i+1:]...) 1078 i-- 1079 } 1080 } 1081 for i := 0; i < len(blobs); i++ { 1082 for j := i + 1; j < len(blobs); j++ { 1083 if blobs[i].Properties.LastModified.After(*blobs[j].Properties.LastModified) { 1084 blobs[i], blobs[j] = blobs[j], blobs[i] 1085 } 1086 } 1087 } 1088 // Filter out all archives more recent that the given threshold 1089 for i, blob := range blobs { 1090 if time.Since(*blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour { 1091 blobs = blobs[:i] 1092 break 1093 } 1094 } 1095 fmt.Printf("Deleting %d blobs\n", len(blobs)) 1096 // Delete all marked as such and return 1097 if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { 1098 log.Fatal(err) 1099 } 1100 } 1101 1102 func doSanityCheck() { 1103 build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt")) 1104 }