github.com/klaytn/klaytn@v1.12.1/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 package main 21 22 import ( 23 "bufio" 24 "bytes" 25 "encoding/base64" 26 "flag" 27 "fmt" 28 "go/parser" 29 "go/token" 30 "log" 31 "os" 32 "os/exec" 33 "path/filepath" 34 "regexp" 35 "runtime" 36 "strconv" 37 "strings" 38 "time" 39 40 "github.com/klaytn/klaytn/utils/build" 41 ) 42 43 var ( 44 45 // Files that end up in the klay*.zip archive. 46 klayArchiveFiles = []string{ 47 "COPYING", 48 executablePath("klay"), 49 } 50 51 // Files that end up in the klay-alltools*.zip archive. 52 allToolsArchiveFiles = []string{ 53 "COPYING", 54 executablePath("klay"), 55 } 56 57 // A debian package is created for all executables listed here. 58 debExecutables = []debExecutable{ 59 { 60 Name: "klay", 61 Description: "Klaytn CLI client.", 62 }, 63 } 64 65 // Distros for which packages are created. 66 // Note: vivid is unsupported because there is no golang-1.6 package for it. 67 // Note: wily is unsupported because it was officially deprecated on lanchpad. 68 // Note: yakkety is unsupported because it was officially deprecated on lanchpad. 69 // Note: zesty is unsupported because it was officially deprecated on lanchpad. 70 debDistros = []string{"trusty", "xenial", "artful", "bionic"} 71 ) 72 73 var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) 74 75 func executablePath(name string) string { 76 if runtime.GOOS == "windows" { 77 name += ".exe" 78 } 79 return filepath.Join(GOBIN, name) 80 } 81 82 func main() { 83 log.SetFlags(log.Lshortfile) 84 85 if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) { 86 log.Fatal("this script must be run from the root of the repository") 87 } 88 if len(os.Args) < 2 { 89 log.Fatal("need subcommand as first argument") 90 } 91 switch os.Args[1] { 92 case "install": 93 doInstall(os.Args[2:]) 94 case "test": 95 doTest(os.Args[2:]) 96 case "cover": 97 doCover(os.Args[2:]) 98 case "lint": 99 doLint(os.Args[2:], true) 100 case "lint-try": 101 doLint(os.Args[2:], false) 102 case "archive": 103 doArchive(os.Args[2:]) 104 case "debsrc": 105 doDebianSource(os.Args[2:]) 106 case "nsis": 107 doWindowsInstaller(os.Args[2:]) 108 case "aar": 109 doAndroidArchive(os.Args[2:]) 110 case "xcode": 111 doXCodeFramework(os.Args[2:]) 112 default: 113 log.Fatal("unknown command ", os.Args[1]) 114 } 115 } 116 117 // Compiling 118 119 func doInstall(cmdline []string) { 120 var ( 121 arch = flag.String("arch", "", "Architecture to cross build for") 122 cc = flag.String("cc", "", "C compiler to cross build with") 123 tags = flag.String("tags", "", "Build tags") 124 ) 125 flag.CommandLine.Parse(cmdline) 126 env := build.Env() 127 128 var tagsArgs []string 129 if len(*tags) > 0 { 130 tagsArgs = append(tagsArgs, "-tags", *tags) 131 } 132 133 // Check Go version. People regularly open issues about compilation 134 // failure with outdated Go. This should save them the trouble. 135 if !strings.Contains(runtime.Version(), "devel") { 136 // Figure out the minor version number since we can't textually compare (1.10 < 1.9) 137 var minor int 138 fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) 139 140 if minor < 9 { 141 log.Println("You have Go version", runtime.Version()) 142 log.Println("klaytn requires at least Go version 1.9 and cannot") 143 log.Println("be compiled with an earlier version. Please upgrade your Go installation.") 144 os.Exit(1) 145 } 146 } 147 // Compile packages given as arguments, or everything if there are no arguments. 148 packages := []string{"./..."} 149 if flag.NArg() > 0 { 150 packages = flag.Args() 151 } 152 153 if *arch == "" || *arch == runtime.GOARCH { 154 goinstall := goTool("install", buildFlags(env)...) 155 goinstall.Args = append(goinstall.Args, "-v") 156 goinstall.Args = append(goinstall.Args, tagsArgs...) 157 // goinstall.Args = append(goinstall.Args, "-race") 158 goinstall.Args = append(goinstall.Args, packages...) 159 build.MustRun(goinstall) 160 return 161 } 162 // If we are cross compiling to ARMv5 ARMv6 or ARMv7, clean any previous builds 163 if *arch == "arm" { 164 os.RemoveAll(filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_arm")) 165 for _, path := range filepath.SplitList(build.GOPATH()) { 166 os.RemoveAll(filepath.Join(path, "pkg", runtime.GOOS+"_arm")) 167 } 168 } 169 // Seems we are cross compiling, work around forbidden GOBIN 170 goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...) 171 goinstall.Args = append(goinstall.Args, "-v") 172 goinstall.Args = append(goinstall.Args, tagsArgs...) 173 goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...) 174 goinstall.Args = append(goinstall.Args, packages...) 175 build.MustRun(goinstall) 176 177 if cmds, err := os.ReadDir("cmd"); err == nil { 178 for _, cmd := range cmds { 179 pkgs, err := parser.ParseDir(token.NewFileSet(), filepath.Join(".", "cmd", cmd.Name()), nil, parser.PackageClauseOnly) 180 if err != nil { 181 log.Fatal(err) 182 } 183 for name := range pkgs { 184 if name == "main" { 185 gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...) 186 gobuild.Args = append(gobuild.Args, "-v") 187 gobuild.Args = append(goinstall.Args, tagsArgs...) 188 gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...) 189 gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name())) 190 build.MustRun(gobuild) 191 break 192 } 193 } 194 } 195 } 196 } 197 198 func buildFlags(env build.Environment) (flags []string) { 199 var ld []string 200 if env.Commit != "" { 201 ld = append(ld, "-X", "main.gitCommit="+env.Commit) 202 ld = append(ld, "-X", "github.com/klaytn/klaytn/cmd/utils/nodecmd.gitCommit="+env.Commit) 203 } 204 if env.Tag != "" { 205 ld = append(ld, "-X", "github.com/klaytn/klaytn/cmd/utils/nodecmd.gitTag="+env.Tag) 206 } 207 if runtime.GOOS == "darwin" { 208 ld = append(ld, "-s") 209 } 210 211 if env.IsDisabledSymbolTable { 212 ld = append(ld, "-s") 213 } 214 if env.IsStaticLink { 215 // Pass the static link flag to the external linker. 216 // By default, cmd/link will use external linking mode when non-standard cgo packages are involved. 217 ld = append(ld, "-linkmode", "external", "-extldflags", "-static") 218 } 219 if env.IsKlaytnRaceDetectionOn { 220 flags = append(flags, "-race") 221 } 222 if len(ld) > 0 { 223 flags = append(flags, "-ldflags", strings.Join(ld, " ")) 224 } 225 return flags 226 } 227 228 func goTool(subcmd string, args ...string) *exec.Cmd { 229 return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...) 230 } 231 232 func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd { 233 cmd := build.GoTool(subcmd, args...) 234 cmd.Env = []string{"GOPATH=" + build.GOPATH()} 235 if arch == "" || arch == runtime.GOARCH { 236 cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) 237 } else { 238 cmd.Env = append(cmd.Env, "CGO_ENABLED=1") 239 cmd.Env = append(cmd.Env, "GOARCH="+arch) 240 } 241 if cc != "" { 242 cmd.Env = append(cmd.Env, "CC="+cc) 243 } 244 // CKZG by default is not portable, append the necessary build flags to make 245 // it not rely on modern CPU instructions and enable linking against. 246 cmd.Env = append(cmd.Env, "CGO_CFLAGS=-O2 -g -D__BLST_PORTABLE__") 247 for _, e := range os.Environ() { 248 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") { 249 continue 250 } 251 cmd.Env = append(cmd.Env, e) 252 } 253 return cmd 254 } 255 256 // Running The Tests 257 // 258 // "tests" also includes static analysis tools such as vet. 259 260 func doTest(cmdline []string) { 261 var ( 262 parallel = flag.Int("p", 0, "The number of parallel test executions (default: the number of CPUs available)") 263 excludes = flag.String("exclude", "", "Comma-separated top-level directories to be excluded in test") 264 ) 265 flag.CommandLine.Parse(cmdline) 266 env := build.Env() 267 268 packages := []string{"./..."} 269 if len(flag.CommandLine.Args()) > 0 { 270 packages = flag.CommandLine.Args() 271 } 272 273 if *excludes != "" { 274 packages = build.ExcludePackages(packages, strings.Split(*excludes, ",")) 275 } 276 277 // Run analysis tools before the tests. 278 build.MustRun(goTool("vet", packages...)) 279 280 // Run the actual tests. 281 gotest := goTool("test", buildFlags(env)...) 282 if *parallel != 0 { 283 gotest.Args = append(gotest.Args, "-p", strconv.Itoa(*parallel)) 284 } 285 gotest.Args = append(gotest.Args, "--timeout=30m") 286 gotest.Args = append(gotest.Args, packages...) 287 build.MustRun(gotest) 288 } 289 290 func doCover(cmdline []string) { 291 var ( 292 parallel = flag.Int("p", 0, "The number of parallel coverage test executions (default: the number of CPUs available)") 293 excludes = flag.String("exclude", "", "Comma-separated top-level directories to be excluded in coverage test") 294 outputFile = flag.String("coverprofile", "coverage.out", "The coverage profile file will be generated by coverage test") 295 ) 296 flag.CommandLine.Parse(cmdline) 297 env := build.Env() 298 299 packages := []string{"./..."} 300 if len(flag.CommandLine.Args()) > 0 { 301 packages = flag.CommandLine.Args() 302 } 303 304 if *excludes != "" { 305 packages = build.ExcludePackages(packages, strings.Split(*excludes, ",")) 306 } 307 308 coverPackages := []string{"./..."} 309 coverExcludes := []string{ 310 "/tests", 311 "/metric", 312 "/build", 313 "/client", 314 "/contracts", 315 "/simulations", 316 "/api", 317 "/fork", 318 "/mocks", 319 } 320 321 coverPackages = build.ExcludePackages(coverPackages, coverExcludes) 322 coverPackagesString := strings.Join(coverPackages, ",") 323 324 // Run analysis tools before the tests. 325 build.MustRun(goTool("vet", packages...)) 326 327 // Generate a coverage output file. 328 build.MustRunCommand("sh", "-c", "echo 'mode: atomic' > "+*outputFile) 329 330 // Run the actual tests. 331 gotest := goTool("test", buildFlags(env)...) 332 if *parallel != 0 { 333 gotest.Args = append(gotest.Args, "-p", strconv.Itoa(*parallel)) 334 } 335 336 gotest.Args = append(gotest.Args, "-cover", "-covermode=atomic", "-coverprofile="+*outputFile) 337 gotest.Args = append(gotest.Args, "-coverpkg", coverPackagesString) 338 gotest.Args = append(gotest.Args, "--timeout=30m") 339 gotest.Args = append(gotest.Args, packages...) 340 build.MustRun(gotest) 341 } 342 343 // runs gometalinter on requested packages and exits immediately when linter warning observed if exitOnError is true 344 // if exitOnError is false, prepare a report file for linters and run additional linters without stopping 345 func doLint(cmdline []string, exitOnError bool) { 346 var ( 347 vFlag = flag.Bool("v", false, "verbose output") 348 newFromRev = flag.String("new-from-rev", "", "Show only new issues created after git revision REV") 349 350 fname = "linter_report.txt" 351 ) 352 flag.CommandLine.Parse(cmdline) 353 354 packages := []string{"./..."} 355 if len(flag.CommandLine.Args()) > 0 { 356 packages = flag.CommandLine.Args() 357 } 358 359 lintBin := installLinter() 360 361 // linters for "lint" command 362 lintersSet := [][]string{ 363 { 364 "--presets=format", 365 "--presets=performance", 366 }, 367 } 368 369 if !exitOnError { 370 // Prepare a report file for linters 371 fileOut, err := os.Create(fname) 372 defer fileOut.Close() 373 if err != nil { 374 log.Fatal(err) 375 } 376 fmt.Printf("Generating a linter report %s using above linters.\n", fname) 377 378 oldStdout := os.Stdout 379 os.Stdout = fileOut 380 defer func() { 381 // Restore stdout 382 os.Stdout = oldStdout 383 fmt.Printf("Successfully generating %s.\n", fname) 384 }() 385 386 // linters for "lint-try" command 387 lintersSet = [][]string{ 388 { 389 "--enable=misspell", 390 "--enable=goconst", 391 }, 392 { 393 "--enable=dupl", 394 "--enable=errcheck", 395 "--enable=ineffassign", 396 "--enable=unparam", 397 "--enable=unused", 398 }, 399 {"--enable=unconvert"}, 400 {"--enable=gosimple"}, 401 {"--enable=staticcheck"}, 402 {"--enable=gocyclo"}, 403 } 404 } 405 406 for _, linters := range lintersSet { 407 configs := []string{ 408 "run", 409 "--tests", 410 "--disable-all", 411 "--timeout=10m", 412 } 413 if *vFlag { 414 configs = append(configs, "-v") 415 } 416 if *newFromRev != "" { 417 configs = append(configs, "--new-from-rev="+*newFromRev) 418 } 419 configs = append(configs, linters...) 420 args := append(configs, packages...) 421 if exitOnError { 422 build.MustRunCommand(lintBin, args...) 423 } else { 424 build.TryRunCommand(lintBin, args...) 425 } 426 } 427 } 428 429 // Release Packaging 430 func doArchive(cmdline []string) { 431 var ( 432 arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") 433 atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") 434 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) 435 upload = flag.String("upload", "", `Destination to upload the archives`) 436 ext string 437 ) 438 flag.CommandLine.Parse(cmdline) 439 switch *atype { 440 case "zip": 441 ext = ".zip" 442 case "tar": 443 ext = ".tar.gz" 444 default: 445 log.Fatal("unknown archive type: ", atype) 446 } 447 448 var ( 449 env = build.Env() 450 base = archiveBasename(*arch, env) 451 klaybin = "klay-" + base + ext 452 alltools = "klay-alltools-" + base + ext 453 ) 454 maybeSkipArchive(env) 455 if err := build.WriteArchive(klaybin, klayArchiveFiles); err != nil { 456 log.Fatal(err) 457 } 458 if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil { 459 log.Fatal(err) 460 } 461 for _, archive := range []string{klaybin, alltools} { 462 if err := archiveUpload(archive, *upload, *signer); err != nil { 463 log.Fatal(err) 464 } 465 } 466 } 467 468 func archiveBasename(arch string, env build.Environment) string { 469 platform := runtime.GOOS + "-" + arch 470 if arch == "arm" { 471 platform += os.Getenv("GOARM") 472 } 473 if arch == "android" { 474 platform = "android-all" 475 } 476 if arch == "ios" { 477 platform = "ios-all" 478 } 479 return platform + "-" + archiveVersion(env) 480 } 481 482 func archiveVersion(env build.Environment) string { 483 version := build.VERSION() 484 if isUnstableBuild(env) { 485 version += "-unstable" 486 } 487 if env.Commit != "" { 488 version += "-" + env.Commit[:8] 489 } 490 return version 491 } 492 493 func archiveUpload(archive string, blobstore string, signer string) error { 494 // If signing was requested, generate the signature files 495 if signer != "" { 496 pgpkey, err := base64.StdEncoding.DecodeString(os.Getenv(signer)) 497 if err != nil { 498 return fmt.Errorf("invalid base64 %s", signer) 499 } 500 if err := build.PGPSignFile(archive, archive+".asc", string(pgpkey)); err != nil { 501 return err 502 } 503 } 504 return nil 505 } 506 507 // skips archiving for some build configurations. 508 func maybeSkipArchive(env build.Environment) { 509 if env.IsPullRequest { 510 log.Printf("skipping because this is a PR build") 511 os.Exit(0) 512 } 513 if env.IsCronJob { 514 log.Printf("skipping because this is a cron job") 515 os.Exit(0) 516 } 517 if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { 518 log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) 519 os.Exit(0) 520 } 521 } 522 523 // Debian Packaging 524 525 func doDebianSource(cmdline []string) { 526 var ( 527 signer = flag.String("signer", "", `Signing key name, also used as package author`) 528 upload = flag.String("upload", "", `Where to upload the source package (usually "ppa:klaytn/klaytn")`) 529 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 530 now = time.Now() 531 ) 532 flag.CommandLine.Parse(cmdline) 533 *workdir = makeWorkdir(*workdir) 534 env := build.Env() 535 maybeSkipArchive(env) 536 537 // Import the signing key. 538 if b64key := os.Getenv("PPA_SIGNING_KEY"); b64key != "" { 539 key, err := base64.StdEncoding.DecodeString(b64key) 540 if err != nil { 541 log.Fatal("invalid base64 PPA_SIGNING_KEY") 542 } 543 gpg := exec.Command("gpg", "--import") 544 gpg.Stdin = bytes.NewReader(key) 545 build.MustRun(gpg) 546 } 547 548 // Create the packages. 549 for _, distro := range debDistros { 550 meta := newDebMetadata(distro, *signer, env, now) 551 pkgdir := stageDebianSource(*workdir, meta) 552 debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc") 553 debuild.Dir = pkgdir 554 build.MustRun(debuild) 555 556 changes := fmt.Sprintf("%s_%s_source.changes", meta.Name(), meta.VersionString()) 557 changes = filepath.Join(*workdir, changes) 558 if *signer != "" { 559 build.MustRunCommand("debsign", changes) 560 } 561 if *upload != "" { 562 build.MustRunCommand("dput", *upload, changes) 563 } 564 } 565 } 566 567 func makeWorkdir(wdflag string) string { 568 var err error 569 if wdflag != "" { 570 err = os.MkdirAll(wdflag, 0o744) 571 } else { 572 wdflag, err = os.MkdirTemp("", "klay-build-") 573 } 574 if err != nil { 575 log.Fatal(err) 576 } 577 return wdflag 578 } 579 580 func isUnstableBuild(env build.Environment) bool { 581 if env.Tag != "" { 582 return false 583 } 584 return true 585 } 586 587 type debMetadata struct { 588 Env build.Environment 589 590 // klaytn version being built. Note that this 591 // is not the debian package version. The package version 592 // is constructed by VersionString. 593 Version string 594 595 Author string // "name <email>", also selects signing key 596 Distro, Time string 597 Executables []debExecutable 598 } 599 600 type debExecutable struct { 601 Name, Description string 602 } 603 604 func newDebMetadata(distro, author string, env build.Environment, t time.Time) debMetadata { 605 if author == "" { 606 // No signing key, use default author. 607 author = "Klaytn Builds <infra@groundx.xyz>" 608 } 609 return debMetadata{ 610 Env: env, 611 Author: author, 612 Distro: distro, 613 Version: build.VERSION(), 614 Time: t.Format(time.RFC1123Z), 615 Executables: debExecutables, 616 } 617 } 618 619 // Name returns the name of the metapackage that depends 620 // on all executable packages. 621 func (meta debMetadata) Name() string { 622 if isUnstableBuild(meta.Env) { 623 return "klaytn-unstable" 624 } 625 return "klaytn" 626 } 627 628 // VersionString returns the debian version of the packages. 629 func (meta debMetadata) VersionString() string { 630 vsn := meta.Version 631 if meta.Env.Buildnum != "" { 632 vsn += "+build" + meta.Env.Buildnum 633 } 634 if meta.Distro != "" { 635 vsn += "+" + meta.Distro 636 } 637 return vsn 638 } 639 640 // ExeList returns the list of all executable packages. 641 func (meta debMetadata) ExeList() string { 642 names := make([]string, len(meta.Executables)) 643 for i, e := range meta.Executables { 644 names[i] = meta.ExeName(e) 645 } 646 return strings.Join(names, ", ") 647 } 648 649 // ExeName returns the package name of an executable package. 650 func (meta debMetadata) ExeName(exe debExecutable) string { 651 if isUnstableBuild(meta.Env) { 652 return exe.Name + "-unstable" 653 } 654 return exe.Name 655 } 656 657 // ExeConflicts returns the content of the Conflicts field 658 // for executable packages. 659 func (meta debMetadata) ExeConflicts(exe debExecutable) string { 660 if isUnstableBuild(meta.Env) { 661 // Set up the conflicts list so that the *-unstable packages 662 // cannot be installed alongside the regular version. 663 // 664 // https://www.debian.org/doc/debian-policy/ch-relationships.html 665 // is very explicit about Conflicts: and says that Breaks: should 666 // be preferred and the conflicting files should be handled via 667 // alternates. We might do this eventually but using a conflict is 668 // easier now. 669 return "klaytn, " + exe.Name 670 } 671 return "" 672 } 673 674 func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { 675 pkg := meta.Name() + "-" + meta.VersionString() 676 pkgdir = filepath.Join(tmpdir, pkg) 677 if err := os.Mkdir(pkgdir, 0o755); err != nil { 678 log.Fatal(err) 679 } 680 681 // Copy the source code. 682 build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator)) 683 684 // Put the debian build files in place. 685 debian := filepath.Join(pkgdir, "debian") 686 build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0o755, meta) 687 build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0o644, meta) 688 build.Render("build/deb.control", filepath.Join(debian, "control"), 0o644, meta) 689 build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0o644, meta) 690 build.RenderString("8\n", filepath.Join(debian, "compat"), 0o644, meta) 691 build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0o644, meta) 692 for _, exe := range meta.Executables { 693 install := filepath.Join(debian, meta.ExeName(exe)+".install") 694 docs := filepath.Join(debian, meta.ExeName(exe)+".docs") 695 build.Render("build/deb.install", install, 0o644, exe) 696 build.Render("build/deb.docs", docs, 0o644, exe) 697 } 698 699 return pkgdir 700 } 701 702 // Windows installer 703 704 func doWindowsInstaller(cmdline []string) { 705 // Parse the flags and make skip installer generation on PRs 706 var ( 707 arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") 708 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) 709 upload = flag.String("upload", "", `Destination to upload the archives`) 710 workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) 711 ) 712 flag.CommandLine.Parse(cmdline) 713 *workdir = makeWorkdir(*workdir) 714 env := build.Env() 715 maybeSkipArchive(env) 716 717 // Aggregate binaries that are included in the installer 718 var ( 719 devTools []string 720 allTools []string 721 klayTool string 722 ) 723 for _, file := range allToolsArchiveFiles { 724 if file == "COPYING" { // license, copied later 725 continue 726 } 727 allTools = append(allTools, filepath.Base(file)) 728 if filepath.Base(file) == "klay.exe" { 729 klayTool = file 730 } else { 731 devTools = append(devTools, file) 732 } 733 } 734 735 // Render NSIS scripts: Installer NSIS contains two installer sections, 736 // first section contains the klaytn binary, second section holds the dev tools. 737 templateData := map[string]interface{}{ 738 "License": "COPYING", 739 "Klay": klayTool, 740 "DevTools": devTools, 741 } 742 build.Render("build/nsis.klay.nsi", filepath.Join(*workdir, "klay.nsi"), 0o644, nil) 743 build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0o644, templateData) 744 build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0o644, allTools) 745 build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0o644, nil) 746 build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0o644, nil) 747 build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0o755) 748 build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0o755) 749 750 // Build the installer. This assumes that all the needed files have been previously 751 // built (don't mix building and packaging to keep cross compilation complexity to a 752 // minimum). 753 version := strings.Split(build.VERSION(), ".") 754 if env.Commit != "" { 755 version[2] += "-" + env.Commit[:8] 756 } 757 installer, _ := filepath.Abs("klay-" + archiveBasename(*arch, env) + ".exe") 758 build.MustRunCommand("makensis.exe", 759 "/DOUTPUTFILE="+installer, 760 "/DMAJORVERSION="+version[0], 761 "/DMINORVERSION="+version[1], 762 "/DBUILDVERSION="+version[2], 763 "/DARCH="+*arch, 764 filepath.Join(*workdir, "klay.nsi"), 765 ) 766 767 // Sign and publish installer. 768 if err := archiveUpload(installer, *upload, *signer); err != nil { 769 log.Fatal(err) 770 } 771 } 772 773 // Android archives 774 775 func doAndroidArchive(cmdline []string) { 776 var ( 777 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 778 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) 779 deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) 780 upload = flag.String("upload", "", `Destination to upload the archive`) 781 ) 782 flag.CommandLine.Parse(cmdline) 783 env := build.Env() 784 785 // Sanity check that the SDK and NDK are installed and set 786 if os.Getenv("ANDROID_HOME") == "" { 787 log.Fatal("Please ensure ANDROID_HOME points to your Android SDK") 788 } 789 if os.Getenv("ANDROID_NDK") == "" { 790 log.Fatal("Please ensure ANDROID_NDK points to your Android NDK") 791 } 792 // Build the Android archive and Maven resources 793 build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) 794 build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK"))) 795 build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.klaytn", "-v", "github.com/klaytn/klaytn/mobile")) 796 797 if *local { 798 // If we're building locally, copy bundle to build dir and skip Maven 799 os.Rename("klay.aar", filepath.Join(GOBIN, "klay.aar")) 800 return 801 } 802 meta := newMavenMetadata(env) 803 build.Render("build/mvn.pom", meta.Package+".pom", 0o755, meta) 804 805 // Skip Maven deploy and Azure upload for PR builds 806 maybeSkipArchive(env) 807 808 // Sign and upload the archive to Azure 809 archive := "klay-" + archiveBasename("android", env) + ".aar" 810 os.Rename("klay.aar", archive) 811 812 if err := archiveUpload(archive, *upload, *signer); err != nil { 813 log.Fatal(err) 814 } 815 // Sign and upload all the artifacts to Maven Central 816 os.Rename(archive, meta.Package+".aar") 817 if *signer != "" && *deploy != "" { 818 // Import the signing key into the local GPG instance 819 b64key := os.Getenv(*signer) 820 key, err := base64.StdEncoding.DecodeString(b64key) 821 if err != nil { 822 log.Fatalf("invalid base64 %s", *signer) 823 } 824 gpg := exec.Command("gpg", "--import") 825 gpg.Stdin = bytes.NewReader(key) 826 build.MustRun(gpg) 827 828 keyID, err := build.PGPKeyID(string(key)) 829 if err != nil { 830 log.Fatal(err) 831 } 832 // Upload the artifacts to Sonatype and/or Maven Central 833 repo := *deploy + "/service/local/staging/deploy/maven2" 834 if meta.Develop { 835 repo = *deploy + "/content/repositories/snapshots" 836 } 837 build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X", 838 "-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh", 839 "-Dgpg.keyname="+keyID, 840 "-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar") 841 } 842 } 843 844 func gomobileTool(subcmd string, args ...string) *exec.Cmd { 845 cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd) 846 cmd.Args = append(cmd.Args, args...) 847 cmd.Env = []string{ 848 "GOPATH=" + build.GOPATH(), 849 "PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"), 850 } 851 for _, e := range os.Environ() { 852 if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") { 853 continue 854 } 855 cmd.Env = append(cmd.Env, e) 856 } 857 return cmd 858 } 859 860 type mavenMetadata struct { 861 Version string 862 Package string 863 Develop bool 864 Contributors []mavenContributor 865 } 866 867 type mavenContributor struct { 868 Name string 869 Email string 870 } 871 872 func newMavenMetadata(env build.Environment) mavenMetadata { 873 // Collect the list of authors from the repo root 874 contribs := []mavenContributor{} 875 if authors, err := os.Open("AUTHORS"); err == nil { 876 defer authors.Close() 877 878 scanner := bufio.NewScanner(authors) 879 for scanner.Scan() { 880 // Skip any whitespace from the authors list 881 line := strings.TrimSpace(scanner.Text()) 882 if line == "" || line[0] == '#' { 883 continue 884 } 885 // Split the author and insert as a contributor 886 re := regexp.MustCompile("([^<]+) <(.+)>") 887 parts := re.FindStringSubmatch(line) 888 if len(parts) == 3 { 889 contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]}) 890 } 891 } 892 } 893 // Render the version and package strings 894 version := build.VERSION() 895 if isUnstableBuild(env) { 896 version += "-SNAPSHOT" 897 } 898 return mavenMetadata{ 899 Version: version, 900 Package: "klay-" + version, 901 Develop: isUnstableBuild(env), 902 Contributors: contribs, 903 } 904 } 905 906 // XCode frameworks 907 908 func doXCodeFramework(cmdline []string) { 909 var ( 910 local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) 911 signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) 912 deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) 913 upload = flag.String("upload", "", `Destination to upload the archives`) 914 ) 915 flag.CommandLine.Parse(cmdline) 916 env := build.Env() 917 918 // Build the iOS XCode framework 919 build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) 920 build.MustRun(gomobileTool("init")) 921 bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/klaytn/klaytn/mobile") 922 923 if *local { 924 // If we're building locally, use the build folder and stop afterwards 925 bind.Dir, _ = filepath.Abs(GOBIN) 926 build.MustRun(bind) 927 return 928 } 929 archive := "klay-" + archiveBasename("ios", env) 930 if err := os.Mkdir(archive, os.ModePerm); err != nil { 931 log.Fatal(err) 932 } 933 bind.Dir, _ = filepath.Abs(archive) 934 build.MustRun(bind) 935 build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) 936 937 // Skip CocoaPods deploy and Azure upload for PR builds 938 maybeSkipArchive(env) 939 940 // Sign and upload the framework to Azure 941 if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil { 942 log.Fatal(err) 943 } 944 // Prepare and upload a PodSpec to CocoaPods 945 if *deploy != "" { 946 meta := newPodMetadata(env, archive) 947 build.Render("build/pod.podspec", "Klaytn.podspec", 0o755, meta) 948 build.MustRunCommand("pod", *deploy, "push", "Klaytn.podspec", "--allow-warnings", "--verbose") 949 } 950 } 951 952 type podMetadata struct { 953 Version string 954 Commit string 955 Archive string 956 Contributors []podContributor 957 } 958 959 type podContributor struct { 960 Name string 961 Email string 962 } 963 964 func newPodMetadata(env build.Environment, archive string) podMetadata { 965 // Collect the list of authors from the repo root 966 contribs := []podContributor{} 967 if authors, err := os.Open("AUTHORS"); err == nil { 968 defer authors.Close() 969 970 scanner := bufio.NewScanner(authors) 971 for scanner.Scan() { 972 // Skip any whitespace from the authors list 973 line := strings.TrimSpace(scanner.Text()) 974 if line == "" || line[0] == '#' { 975 continue 976 } 977 // Split the author and insert as a contributor 978 re := regexp.MustCompile("([^<]+) <(.+)>") 979 parts := re.FindStringSubmatch(line) 980 if len(parts) == 3 { 981 contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]}) 982 } 983 } 984 } 985 version := build.VERSION() 986 if isUnstableBuild(env) { 987 version += "-unstable." + env.Buildnum 988 } 989 return podMetadata{ 990 Archive: archive, 991 Version: version, 992 Commit: env.Commit, 993 Contributors: contribs, 994 } 995 } 996 997 func installLinter() string { 998 lintBin := filepath.Join(build.GOPATH(), "bin", "golangci-lint") 999 1000 _, err := exec.LookPath(lintBin) 1001 if err != nil { 1002 fmt.Println("Installing golangci-lint.") 1003 1004 cmdCurl := exec.Command("curl", "-sSfL", "https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh") 1005 cmdSh := exec.Command("sh", "-s", "--", "-b", filepath.Join(build.GOPATH(), "bin"), "v1.52.0") 1006 cmdSh.Stdin, err = cmdCurl.StdoutPipe() 1007 if err != nil { 1008 log.Fatal(err) 1009 } 1010 1011 fmt.Println(">>>", strings.Join(cmdCurl.Args, " ")) 1012 if err := cmdCurl.Start(); err != nil { 1013 log.Fatal(err) 1014 } 1015 1016 build.MustRun(cmdSh) 1017 1018 if err := cmdCurl.Wait(); err != nil { 1019 log.Fatal(err) 1020 } 1021 } 1022 return lintBin 1023 }