github.com/core-coin/go-core/v2@v2.1.9/build/ci.go (about) 1 // Copyright 2016 The go-core Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 /* 18 The ci command is called from Continuous Integration scripts. 19 20 Usage: go run build/ci.go <command> <command flags/arguments> 21 22 Available commands are: 23 24 install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables 25 test [ -coverage ] [ packages... ] -- runs the tests 26 lint -- runs certain pre-selected linters 27 archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts 28 importkeys -- imports signing keys from env 29 debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package 30 nsis -- creates a Windows NSIS installer 31 aar [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an Android archive 32 xcode [ -local ] [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an iOS XCode framework 33 xgo [ -alltools ] [ options ] -- cross builds according to options 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 "flag" 43 "fmt" 44 "io" 45 "log" 46 "os" 47 "os/exec" 48 "path" 49 "path/filepath" 50 "runtime" 51 "strings" 52 "time" 53 54 "github.com/core-coin/go-core/v2/internal/build" 55 ) 56 57 var ( 58 // Files that end up in the gocore*.zip archive. 59 gocoreArchiveFiles = []string{ 60 "COPYING", 61 executablePath("gocore"), 62 } 63 64 // Files that end up in the gocore-alltools*.zip archive. 65 allToolsArchiveFiles = []string{ 66 "COPYING", 67 executablePath("abigen"), 68 executablePath("bootnode"), 69 executablePath("cvm"), 70 executablePath("gocore"), 71 executablePath("rlpdump"), 72 executablePath("clef"), 73 } 74 // This is the version of go that will be downloaded by 75 // 76 // go run ci.go install -dlgo 77 dlgoVersion = "1.15.6" 78 ) 79 80 var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) 81 82 func executablePath(name string) string { 83 if runtime.GOOS == "windows" { 84 name += ".exe" 85 } 86 return filepath.Join(GOBIN, name) 87 } 88 89 func main() { 90 log.SetFlags(log.Lshortfile) 91 92 if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) { 93 log.Fatal("this script must be run from the root of the repository") 94 } 95 if len(os.Args) < 2 { 96 log.Fatal("need subcommand as first argument") 97 } 98 switch os.Args[1] { 99 case "install": 100 doInstall(os.Args[2:]) 101 case "test": 102 doTest(os.Args[2:]) 103 case "lint": 104 doLint(os.Args[2:]) 105 case "xgo": 106 doXgo(os.Args[2:]) 107 case "purge": 108 doPurge(os.Args[2:]) 109 default: 110 log.Fatal("unknown command ", os.Args[1]) 111 } 112 } 113 114 // Compiling 115 116 func doInstall(cmdline []string) { 117 var ( 118 dlgo = flag.Bool("dlgo", false, "Download Go and build with it") 119 arch = flag.String("arch", "", "Architecture to cross build for") 120 cc = flag.String("cc", "", "C compiler to cross build with") 121 ) 122 flag.CommandLine.Parse(cmdline) 123 env := build.Env() 124 125 // Check local Go version. People regularly open issues about compilation 126 // failure with outdated Go. This should save them the trouble. 127 if !strings.Contains(runtime.Version(), "devel") { 128 // Figure out the minor version number since we can't textually compare (1.10 < 1.9) 129 var minor int 130 fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) 131 if minor < 13 { 132 log.Println("You have Go version", runtime.Version()) 133 log.Println("go-core requires at least Go version 1.13 and cannot") 134 log.Println("be compiled with an earlier version. Please upgrade your Go installation.") 135 os.Exit(1) 136 } 137 } 138 139 // Choose which go command we're going to use. 140 var gobuild *exec.Cmd 141 if !*dlgo { 142 // Default behavior: use the go version which runs ci.go right now. 143 gobuild = goTool("build") 144 } else { 145 // Download of Go requested. This is for build environments where the 146 // installed version is too old and cannot be upgraded easily. 147 cachedir := filepath.Join("build", "cache") 148 goroot := downloadGo(runtime.GOARCH, runtime.GOOS, cachedir) 149 gobuild = localGoTool(goroot, "build") 150 } 151 152 // Configure environment for cross build. 153 if *arch != "" && *arch != runtime.GOARCH { 154 gobuild.Env = append(gobuild.Env, "CGO_ENABLED=1") 155 gobuild.Env = append(gobuild.Env, "GOARCH="+*arch) 156 } 157 158 // Configure C compiler. 159 if *cc != "" { 160 gobuild.Env = append(gobuild.Env, "CC="+*cc) 161 } else if os.Getenv("CC") != "" { 162 gobuild.Env = append(gobuild.Env, "CC="+os.Getenv("CC")) 163 } 164 165 // arm64 CI builders are memory-constrained and can't handle concurrent builds, 166 // better disable it. This check isn't the best, it should probably 167 // check for something in env instead. 168 if runtime.GOARCH == "arm64" { 169 gobuild.Args = append(gobuild.Args, "-p", "1") 170 } 171 172 // Put the default settings in. 173 gobuild.Args = append(gobuild.Args, buildFlags(env)...) 174 175 // We use -trimpath to avoid leaking local paths into the built executables. 176 gobuild.Args = append(gobuild.Args, "-trimpath") 177 178 // Show packages during build. 179 gobuild.Args = append(gobuild.Args, "-v") 180 181 if runtime.GOOS == "windows" { 182 gobuild.Args = append(gobuild.Args, "-buildmode=exe") 183 } 184 // Now we choose what we're even building. 185 // Default: collect all 'main' packages in cmd/ and build those. 186 packages := flag.Args() 187 if len(packages) == 0 { 188 packages = build.FindMainPackages("./cmd") 189 } 190 191 // Do the build! 192 for _, pkg := range packages { 193 args := make([]string, len(gobuild.Args)) 194 copy(args, gobuild.Args) 195 args = append(args, "-o", executablePath(path.Base(pkg))) 196 args = append(args, pkg) 197 build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) 198 } 199 } 200 201 // buildFlags returns the go tool flags for building. 202 func buildFlags(env build.Environment) (flags []string) { 203 var ld []string 204 if env.Commit != "" { 205 ld = append(ld, "-X", "main.gitTag="+env.Tag) 206 ld = append(ld, "-X", "main.gitCommit="+env.Commit) 207 ld = append(ld, "-X", "main.gitDate="+env.Date) 208 } 209 // Strip DWARF on darwin. This used to be required for certain things, 210 // and there is no downside to this, so we just keep doing it. 211 if runtime.GOOS == "darwin" { 212 ld = append(ld, "-s") 213 } 214 if len(ld) > 0 { 215 flags = append(flags, "-ldflags", strings.Join(ld, " ")) 216 } 217 return flags 218 } 219 220 // goTool returns the go tool. This uses the Go version which runs ci.go. 221 func goTool(subcmd string, args ...string) *exec.Cmd { 222 cmd := build.GoTool(subcmd, args...) 223 goToolSetEnv(cmd) 224 return cmd 225 } 226 227 // localGoTool returns the go tool from the given GOROOT. 228 func localGoTool(goroot string, subcmd string, args ...string) *exec.Cmd { 229 gotool := filepath.Join(goroot, "bin", "go") 230 cmd := exec.Command(gotool, subcmd) 231 goToolSetEnv(cmd) 232 cmd.Env = append(cmd.Env, "GOROOT="+goroot) 233 cmd.Args = append(cmd.Args, args...) 234 return cmd 235 } 236 237 // goToolSetEnv forwards the build environment to the go tool. 238 func goToolSetEnv(cmd *exec.Cmd) { 239 cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) 240 for _, e := range os.Environ() { 241 if strings.HasPrefix(e, "GOBIN=") || strings.HasPrefix(e, "CC=") { 242 continue 243 } 244 cmd.Env = append(cmd.Env, e) 245 } 246 } 247 248 // Running The Tests 249 // 250 // "tests" also includes static analysis tools such as vet. 251 252 func doTest(cmdline []string) { 253 coverage := flag.Bool("coverage", false, "Whether to record code coverage") 254 verbose := flag.Bool("v", false, "Whether to log verbosely") 255 flag.CommandLine.Parse(cmdline) 256 env := build.Env() 257 258 packages := []string{"./..."} 259 if len(flag.CommandLine.Args()) > 0 { 260 packages = flag.CommandLine.Args() 261 } 262 263 // Run the actual tests. 264 // Test a single package at a time. CI builders are slow 265 // and some tests run into timeouts under load. 266 gotest := goTool("test", buildFlags(env)...) 267 gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "20m") 268 if *coverage { 269 gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") 270 } 271 if *verbose { 272 gotest.Args = append(gotest.Args, "-v") 273 } 274 275 gotest.Args = append(gotest.Args, packages...) 276 277 // windows sometimes fails to remove build temp dir 278 if runtime.GOOS == "windows" { 279 fmt.Println(">>>", strings.Join(gotest.Args, " ")) 280 var buf bytes.Buffer 281 gotest.Stderr = &buf 282 gotest.Stdout = &buf 283 284 err := gotest.Run() 285 res := buf.String() 286 fmt.Println(res) 287 if err != nil { 288 if strings.Contains(res, "Access is denied") && !strings.Contains(res, "FAIL") { 289 return 290 } else { 291 log.Fatal(err) 292 } 293 } else { 294 return 295 } 296 } 297 298 build.MustRun(gotest) 299 } 300 301 // doLint runs golangci-lint on requested packages. 302 func doLint(cmdline []string) { 303 var ( 304 cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.") 305 ) 306 flag.CommandLine.Parse(cmdline) 307 packages := []string{"./..."} 308 if len(flag.CommandLine.Args()) > 0 { 309 packages = flag.CommandLine.Args() 310 } 311 312 linter := downloadLinter(*cachedir) 313 lflags := []string{"run", "--config", ".golangci.yml"} 314 build.MustRunCommand(linter, append(lflags, packages...)...) 315 fmt.Println("You have achieved perfection.") 316 } 317 318 // downloadLinter downloads and unpacks golangci-lint. 319 func downloadLinter(cachedir string) string { 320 const version = "1.27.0" 321 322 csdb := build.MustLoadChecksums("build/checksums.txt") 323 base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH) 324 url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s.tar.gz", version, base) 325 archivePath := filepath.Join(cachedir, base+".tar.gz") 326 if err := csdb.DownloadFile(url, archivePath); err != nil { 327 log.Fatal(err) 328 } 329 if err := build.ExtractArchive(archivePath, cachedir); err != nil { 330 log.Fatal(err) 331 } 332 return filepath.Join(cachedir, base, "golangci-lint") 333 } 334 335 // downloadGoSources downloads the Go source tarball. 336 func downloadGoSources(cachedir string) string { 337 csdb := build.MustLoadChecksums("build/checksums.txt") 338 file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) 339 url := "https://dl.google.com/go/" + file 340 dst := filepath.Join(cachedir, file) 341 if err := csdb.DownloadFile(url, dst); err != nil { 342 log.Fatal(err) 343 } 344 return dst 345 } 346 347 // downloadGo downloads the Go binary distribution and unpacks it into a temporary 348 // directory. It returns the GOROOT of the unpacked toolchain. 349 func downloadGo(goarch, goos, cachedir string) string { 350 if goarch == "arm" { 351 goarch = "armv6l" 352 } 353 354 csdb := build.MustLoadChecksums("build/checksums.txt") 355 file := fmt.Sprintf("go%s.%s-%s", dlgoVersion, goos, goarch) 356 if goos == "windows" { 357 file += ".zip" 358 } else { 359 file += ".tar.gz" 360 } 361 url := "https://golang.org/dl/" + file 362 dst := filepath.Join(cachedir, file) 363 if err := csdb.DownloadFile(url, dst); err != nil { 364 log.Fatal(err) 365 } 366 367 ucache, err := os.UserCacheDir() 368 if err != nil { 369 log.Fatal(err) 370 } 371 godir := filepath.Join(ucache, fmt.Sprintf("gocore-go-%s-%s-%s", dlgoVersion, goos, goarch)) 372 if err := build.ExtractArchive(dst, godir); err != nil { 373 log.Fatal(err) 374 } 375 goroot, err := filepath.Abs(filepath.Join(godir, "go")) 376 if err != nil { 377 log.Fatal(err) 378 } 379 return goroot 380 } 381 382 // Cross compilation 383 384 func doXgo(cmdline []string) { 385 var ( 386 alltools = flag.Bool("alltools", false, `Flag whether we're building all known tools, or only on in particular`) 387 ) 388 flag.CommandLine.Parse(cmdline) 389 env := build.Env() 390 391 // Make sure xgo is available for cross compilation 392 gogetxgo := goTool("get", "github.com/karalabe/xgo") 393 build.MustRun(gogetxgo) 394 395 // If all tools building is requested, build everything the builder wants 396 args := append(buildFlags(env), flag.Args()...) 397 398 if *alltools { 399 args = append(args, []string{"--dest", GOBIN}...) 400 for _, res := range allToolsArchiveFiles { 401 if strings.HasPrefix(res, GOBIN) { 402 // Binary tool found, cross build it explicitly 403 args = append(args, "./"+filepath.Join("cmd", filepath.Base(res))) 404 xgo := xgoTool(args) 405 build.MustRun(xgo) 406 args = args[:len(args)-1] 407 } 408 } 409 return 410 } 411 // Otherwise xxecute the explicit cross compilation 412 path := args[len(args)-1] 413 args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...) 414 415 xgo := xgoTool(args) 416 build.MustRun(xgo) 417 } 418 419 func xgoTool(args []string) *exec.Cmd { 420 cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...) 421 cmd.Env = os.Environ() 422 cmd.Env = append(cmd.Env, []string{ 423 "GOBIN=" + GOBIN, 424 }...) 425 return cmd 426 } 427 428 // Binary distribution cleanups 429 430 func doPurge(cmdline []string) { 431 var ( 432 store = flag.String("store", "", `Destination from where to purge archives (usually "gocorestore/builds")`) 433 limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`) 434 ) 435 flag.CommandLine.Parse(cmdline) 436 437 if env := build.Env(); !env.IsCronJob { 438 log.Printf("skipping because not a cron job") 439 os.Exit(0) 440 } 441 // Create the azure authentication and list the current archives 442 auth := build.AzureBlobstoreConfig{ 443 Account: strings.Split(*store, "/")[0], 444 Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), 445 Container: strings.SplitN(*store, "/", 2)[1], 446 } 447 blobs, err := build.AzureBlobstoreList(auth) 448 if err != nil { 449 log.Fatal(err) 450 } 451 fmt.Printf("Found %d blobs\n", len(blobs)) 452 453 // Iterate over the blobs, collect and sort all unstable builds 454 for i := 0; i < len(blobs); i++ { 455 if !strings.Contains(blobs[i].Name, "unstable") { 456 blobs = append(blobs[:i], blobs[i+1:]...) 457 i-- 458 } 459 } 460 for i := 0; i < len(blobs); i++ { 461 for j := i + 1; j < len(blobs); j++ { 462 if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) { 463 blobs[i], blobs[j] = blobs[j], blobs[i] 464 } 465 } 466 } 467 // Filter out all archives more recent that the given threshold 468 for i, blob := range blobs { 469 if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour { 470 blobs = blobs[:i] 471 break 472 } 473 } 474 fmt.Printf("Deleting %d blobs\n", len(blobs)) 475 // Delete all marked as such and return 476 if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { 477 log.Fatal(err) 478 } 479 } 480 481 func captureStdout(f func()) string { 482 old := os.Stdout 483 r, w, _ := os.Pipe() 484 os.Stdout = w 485 486 f() 487 488 w.Close() 489 os.Stdout = old 490 491 var buf bytes.Buffer 492 io.Copy(&buf, r) 493 return buf.String() 494 }