github.com/LampardNguyen234/go-ethereum@v1.10.16-0.20220117140830-b6a3b0260724/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 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 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 "io/ioutil" 50 "log" 51 "os" 52 "os/exec" 53 "path" 54 "path/filepath" 55 "regexp" 56 "runtime" 57 "strconv" 58 "strings" 59 "time" 60 61 "github.com/LampardNguyen234/go-ethereum/crypto/signify" 62 "github.com/LampardNguyen234/go-ethereum/internal/build" 63 "github.com/LampardNguyen234/go-ethereum/params" 64 "github.com/cespare/cp" 65 ) 66 67 var ( 68 // Files that end up in the geth*.zip archive. 69 gethArchiveFiles = []string{ 70 "COPYING", 71 executablePath("geth"), 72 } 73 74 // Files that end up in the geth-alltools*.zip archive. 75 allToolsArchiveFiles = []string{ 76 "COPYING", 77 executablePath("abigen"), 78 executablePath("bootnode"), 79 executablePath("evm"), 80 executablePath("geth"), 81 executablePath("puppeth"), 82 executablePath("rlpdump"), 83 executablePath("clef"), 84 } 85 86 // A debian package is created for all executables listed here. 87 debExecutables = []debExecutable{ 88 { 89 BinaryName: "abigen", 90 Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.", 91 }, 92 { 93 BinaryName: "bootnode", 94 Description: "Ethereum bootnode.", 95 }, 96 { 97 BinaryName: "evm", 98 Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.", 99 }, 100 { 101 BinaryName: "geth", 102 Description: "Ethereum CLI client.", 103 }, 104 { 105 BinaryName: "puppeth", 106 Description: "Ethereum private network manager.", 107 }, 108 { 109 BinaryName: "rlpdump", 110 Description: "Developer utility tool that prints RLP structures.", 111 }, 112 { 113 BinaryName: "clef", 114 Description: "Ethereum account management tool.", 115 }, 116 } 117 118 // A debian package is created for all executables listed here. 119 debEthereum = debPackage{ 120 Name: "ethereum", 121 Version: params.Version, 122 Executables: debExecutables, 123 } 124 125 // Debian meta packages to build and push to Ubuntu PPA 126 debPackages = []debPackage{ 127 debEthereum, 128 } 129 130 // Distros for which packages are created. 131 // Note: vivid is unsupported because there is no golang-1.6 package for it. 132 // Note: the following Ubuntu releases have been officially deprecated on Launchpad: 133 // wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy 134 debDistroGoBoots = map[string]string{ 135 "trusty": "golang-1.11", 136 "xenial": "golang-go", 137 "bionic": "golang-go", 138 "focal": "golang-go", 139 "hirsute": "golang-go", 140 } 141 142 debGoBootPaths = map[string]string{ 143 "golang-1.11": "/usr/lib/go-1.11", 144 "golang-go": "/usr/lib/go", 145 } 146 147 // This is the version of go that will be downloaded by 148 // 149 // go run ci.go install -dlgo 150 dlgoVersion = "1.17.5" 151 ) 152 153 var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) 154 155 func executablePath(name string) string { 156 if runtime.GOOS == "windows" { 157 name += ".exe" 158 } 159 return filepath.Join(GOBIN, name) 160 } 161 162 func main() { 163 log.SetFlags(log.Lshortfile) 164 165 if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) { 166 log.Fatal("this script must be run from the root of the repository") 167 } 168 if len(os.Args) < 2 { 169 log.Fatal("need subcommand as first argument") 170 } 171 switch os.Args[1] { 172 case "install": 173 doInstall(os.Args[2:]) 174 case "test": 175 doTest(os.Args[2:]) 176 case "lint": 177 doLint(os.Args[2:]) 178 case "archive": 179 doArchive(os.Args[2:]) 180 case "docker": 181 doDocker(os.Args[2:]) 182 case "debsrc": 183 doDebianSource(os.Args[2:]) 184 case "nsis": 185 doWindowsInstaller(os.Args[2:]) 186 case "aar": 187 doAndroidArchive(os.Args[2:]) 188 case "xcode": 189 doXCodeFramework(os.Args[2:]) 190 case "purge": 191 doPurge(os.Args[2:]) 192 default: 193 log.Fatal("unknown command ", os.Args[1]) 194 } 195 } 196 197 // Compiling 198 199 func doInstall(cmdline []string) { 200 var ( 201 dlgo = flag.Bool("dlgo", false, "Download Go and build with it") 202 arch = flag.String("arch", "", "Architecture to cross build for") 203 cc = flag.String("cc", "", "C compiler to cross build with") 204 ) 205 flag.CommandLine.Parse(cmdline) 206 207 // Configure the toolchain. 208 tc := build.GoToolchain{GOARCH: *arch, CC: *cc} 209 if *dlgo { 210 csdb := build.MustLoadChecksums("build/checksums.txt") 211 tc.Root = build.DownloadGo(csdb, dlgoVersion) 212 } 213 214 // Configure the build. 215 env := build.Env() 216 gobuild := tc.Go("build", buildFlags(env)...) 217 218 // arm64 CI builders are memory-constrained and can't handle concurrent builds, 219 // better disable it. This check isn't the best, it should probably 220 // check for something in env instead. 221 if env.CI && runtime.GOARCH == "arm64" { 222 gobuild.Args = append(gobuild.Args, "-p", "1") 223 } 224 225 // We use -trimpath to avoid leaking local paths into the built executables. 226 gobuild.Args = append(gobuild.Args, "-trimpath") 227 228 // Show packages during build. 229 gobuild.Args = append(gobuild.Args, "-v") 230 231 // Now we choose what we're even building. 232 // Default: collect all 'main' packages in cmd/ and build those. 233 packages := flag.Args() 234 if len(packages) == 0 { 235 packages = build.FindMainPackages("./cmd") 236 } 237 238 // Do the build! 239 for _, pkg := range packages { 240 args := make([]string, len(gobuild.Args)) 241 copy(args, gobuild.Args) 242 args = append(args, "-o", executablePath(path.Base(pkg))) 243 args = append(args, pkg) 244 build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) 245 } 246 } 247 248 // buildFlags returns the go tool flags for building. 249 func buildFlags(env build.Environment) (flags []string) { 250 var ld []string 251 if env.Commit != "" { 252 ld = append(ld, "-X", "main.gitCommit="+env.Commit) 253 ld = append(ld, "-X", "main.gitDate="+env.Date) 254 } 255 // Strip DWARF on darwin. This used to be required for certain things, 256 // and there is no downside to this, so we just keep doing it. 257 if runtime.GOOS == "darwin" { 258 ld = append(ld, "-s") 259 } 260 // Enforce the stacksize to 8M, which is the case on most platforms apart from 261 // alpine Linux. 262 if runtime.GOOS == "linux" { 263 ld = append(ld, "-extldflags", "-Wl,-z,stack-size=0x800000") 264 } 265 if len(ld) > 0 { 266 flags = append(flags, "-ldflags", strings.Join(ld, " ")) 267 } 268 return flags 269 } 270 271 // Running The Tests 272 // 273 // "tests" also includes static analysis tools such as vet. 274 275 func doTest(cmdline []string) { 276 var ( 277 dlgo = flag.Bool("dlgo", false, "Download Go and build with it") 278 arch = flag.String("arch", "", "Run tests for given architecture") 279 cc = flag.String("cc", "", "Sets C compiler binary") 280 coverage = flag.Bool("coverage", false, "Whether to record code coverage") 281 verbose = flag.Bool("v", false, "Whether to log verbosely") 282 race = flag.Bool("race", false, "Execute the race detector") 283 ) 284 flag.CommandLine.Parse(cmdline) 285 286 // Configure the toolchain. 287 tc := build.GoToolchain{GOARCH: *arch, CC: *cc} 288 if *dlgo { 289 csdb := build.MustLoadChecksums("build/checksums.txt") 290 tc.Root = build.DownloadGo(csdb, dlgoVersion) 291 } 292 gotest := tc.Go("test") 293 294 // Test a single package at a time. CI builders are slow 295 // and some tests run into timeouts under load. 296 gotest.Args = append(gotest.Args, "-p", "1") 297 if *coverage { 298 gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") 299 } 300 if *verbose { 301 gotest.Args = append(gotest.Args, "-v") 302 } 303 if *race { 304 gotest.Args = append(gotest.Args, "-race") 305 } 306 307 packages := []string{"./..."} 308 if len(flag.CommandLine.Args()) > 0 { 309 packages = flag.CommandLine.Args() 310 } 311 gotest.Args = append(gotest.Args, packages...) 312 build.MustRun(gotest) 313 } 314 315 // doLint runs golangci-lint on requested packages. 316 func doLint(cmdline []string) { 317 var ( 318 cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.") 319 ) 320 flag.CommandLine.Parse(cmdline) 321 packages := []string{"./..."} 322 if len(flag.CommandLine.Args()) > 0 { 323 packages = flag.CommandLine.Args() 324 } 325 326 linter := downloadLinter(*cachedir) 327 lflags := []string{"run", "--config", ".golangci.yml"} 328 build.MustRunCommand(linter, append(lflags, packages...)...) 329 fmt.Println("You have achieved perfection.") 330 } 331 332 // downloadLinter downloads and unpacks golangci-lint. 333 func downloadLinter(cachedir string) string { 334 const version = "1.42.0" 335 336 csdb := build.MustLoadChecksums("build/checksums.txt") 337 base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH) 338 url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s.tar.gz", version, base) 339 archivePath := filepath.Join(cachedir, base+".tar.gz") 340 if err := csdb.DownloadFile(url, archivePath); err != nil { 341 log.Fatal(err) 342 } 343 if err := build.ExtractArchive(archivePath, cachedir); err != nil { 344 log.Fatal(err) 345 } 346 return filepath.Join(cachedir, base, "golangci-lint") 347 } 348 349 // Release Packaging 350 func doArchive(cmdline []string) { 351 var ( 352 arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") 353 atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") 354 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) 355 signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_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 basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit)) 372 geth = "geth-" + basegeth + ext 373 alltools = "geth-alltools-" + basegeth + 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, *signify); err != nil { 384 log.Fatal(err) 385 } 386 } 387 } 388 389 func archiveBasename(arch string, archiveVersion string) 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 401 } 402 403 func archiveUpload(archive string, blobstore string, signer string, signifyVar string) error { 404 // If signing was requested, generate the signature files 405 if signer != "" { 406 key := getenvBase64(signer) 407 if err := build.PGPSignFile(archive, archive+".asc", string(key)); err != nil { 408 return err 409 } 410 } 411 if signifyVar != "" { 412 key := os.Getenv(signifyVar) 413 untrustedComment := "verify with geth-release.pub" 414 trustedComment := fmt.Sprintf("%s (%s)", archive, time.Now().UTC().Format(time.RFC1123)) 415 if err := signify.SignFile(archive, archive+".sig", key, untrustedComment, trustedComment); err != nil { 416 return err 417 } 418 } 419 // If uploading to Azure was requested, push the archive possibly with its signature 420 if blobstore != "" { 421 auth := build.AzureBlobstoreConfig{ 422 Account: strings.Split(blobstore, "/")[0], 423 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 424 Container: strings.SplitN(blobstore, "/", 2)[1], 425 } 426 if err := build.AzureBlobstoreUpload(archive, filepath.Base(archive), auth); err != nil { 427 return err 428 } 429 if signer != "" { 430 if err := build.AzureBlobstoreUpload(archive+".asc", filepath.Base(archive+".asc"), auth); err != nil { 431 return err 432 } 433 } 434 if signifyVar != "" { 435 if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { 436 return err 437 } 438 } 439 } 440 return nil 441 } 442 443 // skips archiving for some build configurations. 444 func maybeSkipArchive(env build.Environment) { 445 if env.IsPullRequest { 446 log.Printf("skipping archive creation because this is a PR build") 447 os.Exit(0) 448 } 449 if env.IsCronJob { 450 log.Printf("skipping archive creation because this is a cron job") 451 os.Exit(0) 452 } 453 if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { 454 log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag) 455 os.Exit(0) 456 } 457 } 458 459 // Builds the docker images and optionally uploads them to Docker Hub. 460 func doDocker(cmdline []string) { 461 var ( 462 image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`) 463 manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`) 464 upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) 465 ) 466 flag.CommandLine.Parse(cmdline) 467 468 // Skip building and pushing docker images for PR builds 469 env := build.Env() 470 maybeSkipArchive(env) 471 472 // Retrieve the upload credentials and authenticate 473 user := getenvBase64("DOCKER_HUB_USERNAME") 474 pass := getenvBase64("DOCKER_HUB_PASSWORD") 475 476 if len(user) > 0 && len(pass) > 0 { 477 auther := exec.Command("docker", "login", "-u", string(user), "--password-stdin") 478 auther.Stdin = bytes.NewReader(pass) 479 build.MustRun(auther) 480 } 481 // Retrieve the version infos to build and push to the following paths: 482 // - ethereum/client-go:latest - Pushes to the master branch, Geth only 483 // - ethereum/client-go:stable - Version tag publish on GitHub, Geth only 484 // - ethereum/client-go:alltools-latest - Pushes to the master branch, Geth & tools 485 // - ethereum/client-go:alltools-stable - Version tag publish on GitHub, Geth & tools 486 // - ethereum/client-go:release-<major>.<minor> - Version tag publish on GitHub, Geth only 487 // - ethereum/client-go:alltools-release-<major>.<minor> - Version tag publish on GitHub, Geth & tools 488 // - ethereum/client-go:v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth only 489 // - ethereum/client-go:alltools-v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth & tools 490 var tags []string 491 492 switch { 493 case env.Branch == "master": 494 tags = []string{"latest"} 495 case strings.HasPrefix(env.Tag, "v1."): 496 tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version} 497 } 498 // If architecture specific image builds are requested, build and push them 499 if *image { 500 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), ".") 501 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", ".") 502 503 // Tag and upload the images to Docker Hub 504 for _, tag := range tags { 505 gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH) 506 toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH) 507 508 // If the image already exists (non version tag), check the build 509 // number to prevent overwriting a newer commit if concurrent builds 510 // are running. This is still a tiny bit racey if two published are 511 // done at the same time, but that's extremely unlikely even on the 512 // master branch. 513 for _, img := range []string{gethImage, toolImage} { 514 if exec.Command("docker", "pull", img).Run() != nil { 515 continue // Generally the only failure is a missing image, which is good 516 } 517 buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() 518 if err != nil { 519 log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) 520 } 521 buildnum = bytes.TrimSpace(buildnum) 522 523 if len(buildnum) > 0 && len(env.Buildnum) > 0 { 524 oldnum, err := strconv.Atoi(string(buildnum)) 525 if err != nil { 526 log.Fatalf("Failed to parse old image build number: %v", err) 527 } 528 newnum, err := strconv.Atoi(env.Buildnum) 529 if err != nil { 530 log.Fatalf("Failed to parse current build number: %v", err) 531 } 532 if oldnum > newnum { 533 log.Fatalf("Current build number %d not newer than existing %d", newnum, oldnum) 534 } else { 535 log.Printf("Updating %s from build %d to %d", img, oldnum, newnum) 536 } 537 } 538 } 539 build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), gethImage) 540 build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), toolImage) 541 build.MustRunCommand("docker", "push", gethImage) 542 build.MustRunCommand("docker", "push", toolImage) 543 } 544 } 545 // If multi-arch image manifest push is requested, assemble it 546 if len(*manifest) != 0 { 547 // Since different architectures are pushed by different builders, wait 548 // until all required images are updated. 549 var mismatch bool 550 for i := 0; i < 2; i++ { // 2 attempts, second is race check 551 mismatch = false // hope there's no mismatch now 552 553 for _, tag := range tags { 554 for _, arch := range strings.Split(*manifest, ",") { 555 gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, arch) 556 toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, arch) 557 558 for _, img := range []string{gethImage, toolImage} { 559 if out, err := exec.Command("docker", "pull", img).CombinedOutput(); err != nil { 560 log.Printf("Required image %s unavailable: %v\nOutput: %s", img, err, out) 561 mismatch = true 562 break 563 } 564 buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() 565 if err != nil { 566 log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) 567 } 568 buildnum = bytes.TrimSpace(buildnum) 569 570 if string(buildnum) != env.Buildnum { 571 log.Printf("Build number mismatch on %s: want %s, have %s", img, env.Buildnum, buildnum) 572 mismatch = true 573 break 574 } 575 } 576 if mismatch { 577 break 578 } 579 } 580 if mismatch { 581 break 582 } 583 } 584 if mismatch { 585 // Build numbers mismatching, retry in a short time to 586 // avoid concurrent failes in both publisher images. If 587 // however the retry failed too, it means the concurrent 588 // builder is still crunching, let that do the publish. 589 if i == 0 { 590 time.Sleep(30 * time.Second) 591 } 592 continue 593 } 594 break 595 } 596 if mismatch { 597 log.Println("Relinquishing publish to other builder") 598 return 599 } 600 // Assemble and push the Geth manifest image 601 for _, tag := range tags { 602 gethImage := fmt.Sprintf("%s:%s", *upload, tag) 603 604 var gethSubImages []string 605 for _, arch := range strings.Split(*manifest, ",") { 606 gethSubImages = append(gethSubImages, gethImage+"-"+arch) 607 } 608 build.MustRunCommand("docker", append([]string{"manifest", "create", gethImage}, gethSubImages...)...) 609 build.MustRunCommand("docker", "manifest", "push", gethImage) 610 } 611 // Assemble and push the alltools manifest image 612 for _, tag := range tags { 613 toolImage := fmt.Sprintf("%s:alltools-%s", *upload, tag) 614 615 var toolSubImages []string 616 for _, arch := range strings.Split(*manifest, ",") { 617 toolSubImages = append(toolSubImages, toolImage+"-"+arch) 618 } 619 build.MustRunCommand("docker", append([]string{"manifest", "create", toolImage}, toolSubImages...)...) 620 build.MustRunCommand("docker", "manifest", "push", toolImage) 621 } 622 } 623 } 624 625 // Debian Packaging 626 func doDebianSource(cmdline []string) { 627 var ( 628 cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`) 629 signer = flag.String("signer", "", `Signing key name, also used as package author`) 630 upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`) 631 sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`) 632 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 633 now = time.Now() 634 ) 635 flag.CommandLine.Parse(cmdline) 636 *workdir = makeWorkdir(*workdir) 637 env := build.Env() 638 tc := new(build.GoToolchain) 639 maybeSkipArchive(env) 640 641 // Import the signing key. 642 if key := getenvBase64("PPA_SIGNING_KEY"); len(key) > 0 { 643 gpg := exec.Command("gpg", "--import") 644 gpg.Stdin = bytes.NewReader(key) 645 build.MustRun(gpg) 646 } 647 648 // Download and verify the Go source package. 649 gobundle := downloadGoSources(*cachedir) 650 651 // Download all the dependencies needed to build the sources and run the ci script 652 srcdepfetch := tc.Go("mod", "download") 653 srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) 654 build.MustRun(srcdepfetch) 655 656 cidepfetch := tc.Go("run", "./build/ci.go") 657 cidepfetch.Env = append(cidepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) 658 cidepfetch.Run() // Command fails, don't care, we only need the deps to start it 659 660 // Create Debian packages and upload them. 661 for _, pkg := range debPackages { 662 for distro, goboot := range debDistroGoBoots { 663 // Prepare the debian package with the go-ethereum sources. 664 meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) 665 pkgdir := stageDebianSource(*workdir, meta) 666 667 // Add Go source code 668 if err := build.ExtractArchive(gobundle, pkgdir); err != nil { 669 log.Fatalf("Failed to extract Go sources: %v", err) 670 } 671 if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { 672 log.Fatalf("Failed to rename Go source folder: %v", err) 673 } 674 // Add all dependency modules in compressed form 675 os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755) 676 if err := cp.CopyAll(filepath.Join(pkgdir, ".mod", "cache", "download"), filepath.Join(*workdir, "modgopath", "pkg", "mod", "cache", "download")); err != nil { 677 log.Fatalf("Failed to copy Go module dependencies: %v", err) 678 } 679 // Run the packaging and upload to the PPA 680 debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz", "-nc") 681 debuild.Dir = pkgdir 682 build.MustRun(debuild) 683 684 var ( 685 basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) 686 source = filepath.Join(*workdir, basename+".tar.xz") 687 dsc = filepath.Join(*workdir, basename+".dsc") 688 changes = filepath.Join(*workdir, basename+"_source.changes") 689 buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo") 690 ) 691 if *signer != "" { 692 build.MustRunCommand("debsign", changes) 693 } 694 if *upload != "" { 695 ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo}) 696 } 697 } 698 } 699 } 700 701 // downloadGoSources downloads the Go source tarball. 702 func downloadGoSources(cachedir string) string { 703 csdb := build.MustLoadChecksums("build/checksums.txt") 704 file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) 705 url := "https://dl.google.com/go/" + file 706 dst := filepath.Join(cachedir, file) 707 if err := csdb.DownloadFile(url, dst); err != nil { 708 log.Fatal(err) 709 } 710 return dst 711 } 712 713 func ppaUpload(workdir, ppa, sshUser string, files []string) { 714 p := strings.Split(ppa, "/") 715 if len(p) != 2 { 716 log.Fatal("-upload PPA name must contain single /") 717 } 718 if sshUser == "" { 719 sshUser = p[0] 720 } 721 incomingDir := fmt.Sprintf("~%s/ubuntu/%s", p[0], p[1]) 722 // Create the SSH identity file if it doesn't exist. 723 var idfile string 724 if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 { 725 idfile = filepath.Join(workdir, "sshkey") 726 if _, err := os.Stat(idfile); os.IsNotExist(err) { 727 ioutil.WriteFile(idfile, sshkey, 0600) 728 } 729 } 730 // Upload 731 dest := sshUser + "@ppa.launchpad.net" 732 if err := build.UploadSFTP(idfile, dest, incomingDir, files); err != nil { 733 log.Fatal(err) 734 } 735 } 736 737 func getenvBase64(variable string) []byte { 738 dec, err := base64.StdEncoding.DecodeString(os.Getenv(variable)) 739 if err != nil { 740 log.Fatal("invalid base64 " + variable) 741 } 742 return []byte(dec) 743 } 744 745 func makeWorkdir(wdflag string) string { 746 var err error 747 if wdflag != "" { 748 err = os.MkdirAll(wdflag, 0744) 749 } else { 750 wdflag, err = ioutil.TempDir("", "geth-build-") 751 } 752 if err != nil { 753 log.Fatal(err) 754 } 755 return wdflag 756 } 757 758 func isUnstableBuild(env build.Environment) bool { 759 if env.Tag != "" { 760 return false 761 } 762 return true 763 } 764 765 type debPackage struct { 766 Name string // the name of the Debian package to produce, e.g. "ethereum" 767 Version string // the clean version of the debPackage, e.g. 1.8.12, without any metadata 768 Executables []debExecutable // executables to be included in the package 769 } 770 771 type debMetadata struct { 772 Env build.Environment 773 GoBootPackage string 774 GoBootPath string 775 776 PackageName string 777 778 // go-ethereum version being built. Note that this 779 // is not the debian package version. The package version 780 // is constructed by VersionString. 781 Version string 782 783 Author string // "name <email>", also selects signing key 784 Distro, Time string 785 Executables []debExecutable 786 } 787 788 type debExecutable struct { 789 PackageName string 790 BinaryName string 791 Description string 792 } 793 794 // Package returns the name of the package if present, or 795 // fallbacks to BinaryName 796 func (d debExecutable) Package() string { 797 if d.PackageName != "" { 798 return d.PackageName 799 } 800 return d.BinaryName 801 } 802 803 func newDebMetadata(distro, goboot, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata { 804 if author == "" { 805 // No signing key, use default author. 806 author = "Ethereum Builds <fjl@ethereum.org>" 807 } 808 return debMetadata{ 809 GoBootPackage: goboot, 810 GoBootPath: debGoBootPaths[goboot], 811 PackageName: name, 812 Env: env, 813 Author: author, 814 Distro: distro, 815 Version: version, 816 Time: t.Format(time.RFC1123Z), 817 Executables: exes, 818 } 819 } 820 821 // Name returns the name of the metapackage that depends 822 // on all executable packages. 823 func (meta debMetadata) Name() string { 824 if isUnstableBuild(meta.Env) { 825 return meta.PackageName + "-unstable" 826 } 827 return meta.PackageName 828 } 829 830 // VersionString returns the debian version of the packages. 831 func (meta debMetadata) VersionString() string { 832 vsn := meta.Version 833 if meta.Env.Buildnum != "" { 834 vsn += "+build" + meta.Env.Buildnum 835 } 836 if meta.Distro != "" { 837 vsn += "+" + meta.Distro 838 } 839 return vsn 840 } 841 842 // ExeList returns the list of all executable packages. 843 func (meta debMetadata) ExeList() string { 844 names := make([]string, len(meta.Executables)) 845 for i, e := range meta.Executables { 846 names[i] = meta.ExeName(e) 847 } 848 return strings.Join(names, ", ") 849 } 850 851 // ExeName returns the package name of an executable package. 852 func (meta debMetadata) ExeName(exe debExecutable) string { 853 if isUnstableBuild(meta.Env) { 854 return exe.Package() + "-unstable" 855 } 856 return exe.Package() 857 } 858 859 // ExeConflicts returns the content of the Conflicts field 860 // for executable packages. 861 func (meta debMetadata) ExeConflicts(exe debExecutable) string { 862 if isUnstableBuild(meta.Env) { 863 // Set up the conflicts list so that the *-unstable packages 864 // cannot be installed alongside the regular version. 865 // 866 // https://www.debian.org/doc/debian-policy/ch-relationships.html 867 // is very explicit about Conflicts: and says that Breaks: should 868 // be preferred and the conflicting files should be handled via 869 // alternates. We might do this eventually but using a conflict is 870 // easier now. 871 return "ethereum, " + exe.Package() 872 } 873 return "" 874 } 875 876 func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { 877 pkg := meta.Name() + "-" + meta.VersionString() 878 pkgdir = filepath.Join(tmpdir, pkg) 879 if err := os.Mkdir(pkgdir, 0755); err != nil { 880 log.Fatal(err) 881 } 882 // Copy the source code. 883 build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator)) 884 885 // Put the debian build files in place. 886 debian := filepath.Join(pkgdir, "debian") 887 build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta) 888 build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta) 889 build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta) 890 build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta) 891 build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta) 892 build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta) 893 for _, exe := range meta.Executables { 894 install := filepath.Join(debian, meta.ExeName(exe)+".install") 895 docs := filepath.Join(debian, meta.ExeName(exe)+".docs") 896 build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe) 897 build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe) 898 } 899 return pkgdir 900 } 901 902 // Windows installer 903 func doWindowsInstaller(cmdline []string) { 904 // Parse the flags and make skip installer generation on PRs 905 var ( 906 arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") 907 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) 908 signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) 909 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 910 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 911 ) 912 flag.CommandLine.Parse(cmdline) 913 *workdir = makeWorkdir(*workdir) 914 env := build.Env() 915 maybeSkipArchive(env) 916 917 // Aggregate binaries that are included in the installer 918 var ( 919 devTools []string 920 allTools []string 921 gethTool string 922 ) 923 for _, file := range allToolsArchiveFiles { 924 if file == "COPYING" { // license, copied later 925 continue 926 } 927 allTools = append(allTools, filepath.Base(file)) 928 if filepath.Base(file) == "geth.exe" { 929 gethTool = file 930 } else { 931 devTools = append(devTools, file) 932 } 933 } 934 935 // Render NSIS scripts: Installer NSIS contains two installer sections, 936 // first section contains the geth binary, second section holds the dev tools. 937 templateData := map[string]interface{}{ 938 "License": "COPYING", 939 "Geth": gethTool, 940 "DevTools": devTools, 941 } 942 build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil) 943 build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData) 944 build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools) 945 build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil) 946 build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil) 947 if err := cp.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll"); err != nil { 948 log.Fatal("Failed to copy SimpleFC.dll: %v", err) 949 } 950 if err := cp.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING"); err != nil { 951 log.Fatal("Failed to copy copyright note: %v", err) 952 } 953 // Build the installer. This assumes that all the needed files have been previously 954 // built (don't mix building and packaging to keep cross compilation complexity to a 955 // minimum). 956 version := strings.Split(params.Version, ".") 957 if env.Commit != "" { 958 version[2] += "-" + env.Commit[:8] 959 } 960 installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe") 961 build.MustRunCommand("makensis.exe", 962 "/DOUTPUTFILE="+installer, 963 "/DMAJORVERSION="+version[0], 964 "/DMINORVERSION="+version[1], 965 "/DBUILDVERSION="+version[2], 966 "/DARCH="+*arch, 967 filepath.Join(*workdir, "geth.nsi"), 968 ) 969 // Sign and publish installer. 970 if err := archiveUpload(installer, *upload, *signer, *signify); err != nil { 971 log.Fatal(err) 972 } 973 } 974 975 // Android archives 976 977 func doAndroidArchive(cmdline []string) { 978 var ( 979 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 980 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) 981 signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. ANDROID_SIGNIFY_KEY)`) 982 deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) 983 upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) 984 ) 985 flag.CommandLine.Parse(cmdline) 986 env := build.Env() 987 tc := new(build.GoToolchain) 988 989 // Sanity check that the SDK and NDK are installed and set 990 if os.Getenv("ANDROID_HOME") == "" { 991 log.Fatal("Please ensure ANDROID_HOME points to your Android SDK") 992 } 993 994 // Build gomobile. 995 install := tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest") 996 install.Env = append(install.Env) 997 build.MustRun(install) 998 999 // Ensure all dependencies are available. This is required to make 1000 // gomobile bind work because it expects go.sum to contain all checksums. 1001 build.MustRun(tc.Go("mod", "download")) 1002 1003 // Build the Android archive and Maven resources 1004 build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/LampardNguyen234/go-ethereum/mobile")) 1005 1006 if *local { 1007 // If we're building locally, copy bundle to build dir and skip Maven 1008 os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar")) 1009 os.Rename("geth-sources.jar", filepath.Join(GOBIN, "geth-sources.jar")) 1010 return 1011 } 1012 meta := newMavenMetadata(env) 1013 build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta) 1014 1015 // Skip Maven deploy and Azure upload for PR builds 1016 maybeSkipArchive(env) 1017 1018 // Sign and upload the archive to Azure 1019 archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar" 1020 os.Rename("geth.aar", archive) 1021 1022 if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { 1023 log.Fatal(err) 1024 } 1025 // Sign and upload all the artifacts to Maven Central 1026 os.Rename(archive, meta.Package+".aar") 1027 if *signer != "" && *deploy != "" { 1028 // Import the signing key into the local GPG instance 1029 key := getenvBase64(*signer) 1030 gpg := exec.Command("gpg", "--import") 1031 gpg.Stdin = bytes.NewReader(key) 1032 build.MustRun(gpg) 1033 keyID, err := build.PGPKeyID(string(key)) 1034 if err != nil { 1035 log.Fatal(err) 1036 } 1037 // Upload the artifacts to Sonatype and/or Maven Central 1038 repo := *deploy + "/service/local/staging/deploy/maven2" 1039 if meta.Develop { 1040 repo = *deploy + "/content/repositories/snapshots" 1041 } 1042 build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X", 1043 "-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh", 1044 "-Dgpg.keyname="+keyID, 1045 "-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar") 1046 } 1047 } 1048 1049 func gomobileTool(subcmd string, args ...string) *exec.Cmd { 1050 cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd) 1051 cmd.Args = append(cmd.Args, args...) 1052 cmd.Env = []string{ 1053 "PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"), 1054 } 1055 for _, e := range os.Environ() { 1056 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") || strings.HasPrefix(e, "GOBIN=") { 1057 continue 1058 } 1059 cmd.Env = append(cmd.Env, e) 1060 } 1061 cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) 1062 return cmd 1063 } 1064 1065 type mavenMetadata struct { 1066 Version string 1067 Package string 1068 Develop bool 1069 Contributors []mavenContributor 1070 } 1071 1072 type mavenContributor struct { 1073 Name string 1074 Email string 1075 } 1076 1077 func newMavenMetadata(env build.Environment) mavenMetadata { 1078 // Collect the list of authors from the repo root 1079 contribs := []mavenContributor{} 1080 if authors, err := os.Open("AUTHORS"); err == nil { 1081 defer authors.Close() 1082 1083 scanner := bufio.NewScanner(authors) 1084 for scanner.Scan() { 1085 // Skip any whitespace from the authors list 1086 line := strings.TrimSpace(scanner.Text()) 1087 if line == "" || line[0] == '#' { 1088 continue 1089 } 1090 // Split the author and insert as a contributor 1091 re := regexp.MustCompile("([^<]+) <(.+)>") 1092 parts := re.FindStringSubmatch(line) 1093 if len(parts) == 3 { 1094 contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]}) 1095 } 1096 } 1097 } 1098 // Render the version and package strings 1099 version := params.Version 1100 if isUnstableBuild(env) { 1101 version += "-SNAPSHOT" 1102 } 1103 return mavenMetadata{ 1104 Version: version, 1105 Package: "geth-" + version, 1106 Develop: isUnstableBuild(env), 1107 Contributors: contribs, 1108 } 1109 } 1110 1111 // XCode frameworks 1112 1113 func doXCodeFramework(cmdline []string) { 1114 var ( 1115 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 1116 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) 1117 signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. IOS_SIGNIFY_KEY)`) 1118 deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) 1119 upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) 1120 ) 1121 flag.CommandLine.Parse(cmdline) 1122 env := build.Env() 1123 tc := new(build.GoToolchain) 1124 1125 // Build gomobile. 1126 build.MustRun(tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest")) 1127 1128 // Ensure all dependencies are available. This is required to make 1129 // gomobile bind work because it expects go.sum to contain all checksums. 1130 build.MustRun(tc.Go("mod", "download")) 1131 1132 // Build the iOS XCode framework 1133 bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/LampardNguyen234/go-ethereum/mobile") 1134 1135 if *local { 1136 // If we're building locally, use the build folder and stop afterwards 1137 bind.Dir = GOBIN 1138 build.MustRun(bind) 1139 return 1140 } 1141 1142 // Create the archive. 1143 maybeSkipArchive(env) 1144 archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit)) 1145 if err := os.MkdirAll(archive, 0755); err != nil { 1146 log.Fatal(err) 1147 } 1148 bind.Dir, _ = filepath.Abs(archive) 1149 build.MustRun(bind) 1150 build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) 1151 1152 // Sign and upload the framework to Azure 1153 if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil { 1154 log.Fatal(err) 1155 } 1156 // Prepare and upload a PodSpec to CocoaPods 1157 if *deploy != "" { 1158 meta := newPodMetadata(env, archive) 1159 build.Render("build/pod.podspec", "Geth.podspec", 0755, meta) 1160 build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings") 1161 } 1162 } 1163 1164 type podMetadata struct { 1165 Version string 1166 Commit string 1167 Archive string 1168 Contributors []podContributor 1169 } 1170 1171 type podContributor struct { 1172 Name string 1173 Email string 1174 } 1175 1176 func newPodMetadata(env build.Environment, archive string) podMetadata { 1177 // Collect the list of authors from the repo root 1178 contribs := []podContributor{} 1179 if authors, err := os.Open("AUTHORS"); err == nil { 1180 defer authors.Close() 1181 1182 scanner := bufio.NewScanner(authors) 1183 for scanner.Scan() { 1184 // Skip any whitespace from the authors list 1185 line := strings.TrimSpace(scanner.Text()) 1186 if line == "" || line[0] == '#' { 1187 continue 1188 } 1189 // Split the author and insert as a contributor 1190 re := regexp.MustCompile("([^<]+) <(.+)>") 1191 parts := re.FindStringSubmatch(line) 1192 if len(parts) == 3 { 1193 contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]}) 1194 } 1195 } 1196 } 1197 version := params.Version 1198 if isUnstableBuild(env) { 1199 version += "-unstable." + env.Buildnum 1200 } 1201 return podMetadata{ 1202 Archive: archive, 1203 Version: version, 1204 Commit: env.Commit, 1205 Contributors: contribs, 1206 } 1207 } 1208 1209 // Binary distribution cleanups 1210 1211 func doPurge(cmdline []string) { 1212 var ( 1213 store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`) 1214 limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`) 1215 ) 1216 flag.CommandLine.Parse(cmdline) 1217 1218 if env := build.Env(); !env.IsCronJob { 1219 log.Printf("skipping because not a cron job") 1220 os.Exit(0) 1221 } 1222 // Create the azure authentication and list the current archives 1223 auth := build.AzureBlobstoreConfig{ 1224 Account: strings.Split(*store, "/")[0], 1225 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 1226 Container: strings.SplitN(*store, "/", 2)[1], 1227 } 1228 blobs, err := build.AzureBlobstoreList(auth) 1229 if err != nil { 1230 log.Fatal(err) 1231 } 1232 fmt.Printf("Found %d blobs\n", len(blobs)) 1233 1234 // Iterate over the blobs, collect and sort all unstable builds 1235 for i := 0; i < len(blobs); i++ { 1236 if !strings.Contains(blobs[i].Name, "unstable") { 1237 blobs = append(blobs[:i], blobs[i+1:]...) 1238 i-- 1239 } 1240 } 1241 for i := 0; i < len(blobs); i++ { 1242 for j := i + 1; j < len(blobs); j++ { 1243 if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) { 1244 blobs[i], blobs[j] = blobs[j], blobs[i] 1245 } 1246 } 1247 } 1248 // Filter out all archives more recent that the given threshold 1249 for i, blob := range blobs { 1250 if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour { 1251 blobs = blobs[:i] 1252 break 1253 } 1254 } 1255 fmt.Printf("Deleting %d blobs\n", len(blobs)) 1256 // Delete all marked as such and return 1257 if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { 1258 log.Fatal(err) 1259 } 1260 }