diff --git a/.gitignore b/.gitignore index 1a85f2f558a2..3ee7e66bfe84 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ build/_vendor/pkg # used by the Makefile /build/_workspace/ +/build/cache/ /build/bin/ /XDC*.zip diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000000..3f8fb3078236 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,79 @@ +# This file configures github.com/golangci/golangci-lint. + +run: + timeout: 20m + tests: true + +linters: + disable-all: true + enable: + - goimports + - gosimple + - govet + - ineffassign + - misspell + - unconvert + - typecheck + - unused + - staticcheck + - bidichk + - durationcheck + - copyloopvar + - whitespace + - revive # only certain checks enabled + - durationcheck + - gocheckcompilerdirectives + - reassign + - mirror + - tenv + ### linters we tried and will not be using: + ### + # - structcheck # lots of false positives + # - errcheck #lot of false positives + # - contextcheck + # - errchkjson # lots of false positives + # - errorlint # this check crashes + # - exhaustive # silly check + # - makezero # false positives + # - nilerr # several intentional + +linters-settings: + gofmt: + simplify: true + revive: + enable-all-rules: false + # here we enable specific useful rules + # see https://golangci-lint.run/usage/linters/#revive for supported rules + rules: + - name: receiver-naming + severity: warning + disabled: false + exclude: [""] + +issues: + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + exclude-dirs-use-default: true + exclude-files: + - core/genesis_alloc.go + exclude-rules: + - path: crypto/bn256/cloudflare/optate.go + linters: + - deadcode + - staticcheck + - path: crypto/bn256/ + linters: + - revive + - path: cmd/utils/flags.go + text: "SA1019: cfg.TxLookupLimit is deprecated: use 'TransactionHistory' instead." + - path: cmd/utils/flags.go + text: "SA1019: ethconfig.Defaults.TxLookupLimit is deprecated: use 'TransactionHistory' instead." + - path: internal/build/pgp.go + text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.' + - path: core/vm/contracts.go + text: 'SA1019: "golang.org/x/crypto/ripemd160" is deprecated: RIPEMD-160 is a legacy hash and should not be used for new applications.' + exclude: + - 'SA1019: event.TypeMux is deprecated: use Feed' + - 'SA1019: strings.Title is deprecated' + - 'SA1019: strings.Title has been deprecated since Go 1.18 and an alternative has been available since Go 1.0: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead.' + - 'SA1029: should not use built-in type string as key for value' diff --git a/Makefile b/Makefile index 3bd726906ce2..e92835b952a0 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +# This Makefile is meant to be used by people that do not usually work +# with Go source code. If you know what GOPATH is then you probably +# don't need to bother with make. + .PHONY: XDC XDC-cross evm all test clean .PHONY: XDC-linux XDC-linux-386 XDC-linux-amd64 XDC-linux-mips64 XDC-linux-mips64le .PHONY: XDC-darwin XDC-darwin-386 XDC-darwin-amd64 @@ -5,11 +9,12 @@ GOBIN = $(shell pwd)/build/bin GOFMT = gofmt GO ?= 1.22.10 +GORUN = go run GO_PACKAGES = . GO_FILES := $(shell find $(shell go list -f '{{.Dir}}' $(GO_PACKAGES)) -name \*.go) - GIT = git +#? XDC: Build XDC. XDC: go run build/ci.go install ./cmd/XDC @echo "Done building." @@ -41,15 +46,26 @@ puppeth: @echo "Done building." @echo "Run \"$(GOBIN)/puppeth\" to launch puppeth." +#? all: Build all packages and executables. all: go run build/ci.go install +#? test: Run the tests. test: all go run build/ci.go test +#? lint: Run certain pre-selected linters. +lint: ## Run linters. + $(GORUN) build/ci.go lint + +#? clean: Clean go cache, built executables, and the auto generated folder. clean: rm -fr build/_workspace/pkg/ $(GOBIN)/* +#? fmt: Ensure consistent code formatting. +fmt: + gofmt -s -w $(shell find . -name "*.go") + # Cross Compilation Targets (xgo) XDC-cross: XDC-windows-amd64 XDC-darwin-amd64 XDC-linux diff --git a/build/checksums.txt b/build/checksums.txt new file mode 100644 index 000000000000..1484e6499f65 --- /dev/null +++ b/build/checksums.txt @@ -0,0 +1,126 @@ +# This file contains sha256 checksums of optional build dependencies. + +# version:spec-tests 2.1.0 +# https://github.com/ethereum/execution-spec-tests/releases +# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ +ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz + +# version:golang 1.22.10 +# https://go.dev/dl/ +1e94fd48be750d1fafb4d9b3b6dd31a6e9d2735d339bf2462bc97b64ca4c1037 go1.22.10.src.tar.gz +dd2c4ac3702658c2c20e3a8b394da1917d86156b2cb4312c9d2f657f80067874 go1.22.10.darwin-amd64.tar.gz +9d37bf96bbdd1669f1ab771c98ac7695f1b49df3c5c2bf10b699950f0fc2f52b go1.22.10.darwin-amd64.pkg +21cf49415ffe0755b45f2b63e75d136528a32f7bb7bdd0166f51d22a03eb0a3f go1.22.10.darwin-arm64.tar.gz +746558a1e677a469b297ed885d94d0cdc3410d2153f49f0109c833f5283e1ad1 go1.22.10.darwin-arm64.pkg +2ae9f00e9621489b75494fa2b8abfc5d09e0cae6effdd4c13867957ad2e4deba go1.22.10.linux-386.tar.gz +736ce492a19d756a92719a6121226087ccd91b652ed5caec40ad6dbfb2252092 go1.22.10.linux-amd64.tar.gz +5213c5e32fde3bd7da65516467b7ffbfe40d2bb5a5f58105e387eef450583eec go1.22.10.linux-arm64.tar.gz +a7bbbc80fe736269820bbdf3555e91ada5d18a5cde2276aac3b559bc1d52fc70 go1.22.10.linux-armv6l.tar.gz +20019b2e60dd0cdf63e4ec26852c1c015c1a27580b32a512b4be33a2539113ae go1.22.10.windows-386.zip +d6f7c00af6d54f09bf5b137cda0b34f75b1ac6fb95abe5a948240110f99d8fad go1.22.10.windows-386.msi +da66f107a0f4959f4615bede230c6bf145a6f01252c6d1ff2b107e293ba339df go1.22.10.windows-amd64.zip +454c45de32d325f175dc9ccd262944aec0d24b12c47a8f6d4000adad9f271adc go1.22.10.windows-amd64.msi +46af3f2506a8887ed1de0e38244730a2ba944cfed7804d6baa9773790d2a294d go1.22.10.aix-ppc64.tar.gz +1684ae4c10c4d30e6f4950fbb71d9cc096d0541e5cf769b3195e91f475249d9a go1.22.10.dragonfly-amd64.tar.gz +f82f5d194114963693e0f51fd56d55d8417ca556438062f2b0df608473b62837 go1.22.10.freebsd-386.tar.gz +cce9da240870a4430c5cf1227bcf29d37575043ff16f7982a69f1139c6f564b5 go1.22.10.freebsd-amd64.tar.gz +abae388d0d42563a242db0d405172cb73e09236f68000ff90c2a327ec8c5764c go1.22.10.freebsd-arm64.tar.gz +7c9c8fe30cbabbb4fb597f0f0ad1279fd2b320bc70831eba4c207b55ad46931d go1.22.10.freebsd-arm.tar.gz +d6f25fd79e17b84d1b61bec3e2fdffc47377b28b51a04b6bdbeac0199313e059 go1.22.10.freebsd-riscv64.tar.gz +4a1121d8331208cb614c6f0474b0e4fa3c3023c5e8a88fb8d37f7e3454589aa2 go1.22.10.illumos-amd64.tar.gz +0be34dbc69726b52414e0283736f997fee477379ebff66cebd7d8c35f4f64f9d go1.22.10.linux-loong64.tar.gz +bb7d7e99db7ee70063cb57bb7395c392b8b5ed87f37d733a1c91de935c70bb91 go1.22.10.linux-mips.tar.gz +c7f0571410297cb29e52d10fed7a2a21aeaeabb9539d0c04a6d778adf0fe7f1b go1.22.10.linux-mips64.tar.gz +e66c440c03dd19bf8423034cbde7f6813321beb18d3fcf2ef234c13a25467952 go1.22.10.linux-mips64le.tar.gz +b4e0061f62a9c1f874893aa4951a4883c19762a9dd64c5710554ec5a7aaf311a go1.22.10.linux-mipsle.tar.gz +4192158cdedc6a124aa32a099cc6bebebabf1f4d380c46c5e36ea52c30a3888b go1.22.10.linux-ppc64.tar.gz +db05b9838f69d741fb9a5301220b1a62014aee025b0baf341aba3d280087b981 go1.22.10.linux-ppc64le.tar.gz +aef9b186c1b9b58c0472dbf54978f97682852a91b2e8d6bf354e59ba9c24438a go1.22.10.linux-riscv64.tar.gz +4ab2286adb096576771801b5099760b1d625fd7b44080449151a4d9b21303672 go1.22.10.linux-s390x.tar.gz +32e66f6ae2082cce98ccb557f54965ae5452e1b705470ae403446062938db1d5 go1.22.10.netbsd-386.tar.gz +e876962e6032175d049259559978c10af578dce506ffb064ce61e60a5b5308cb go1.22.10.netbsd-amd64.tar.gz +b95696f92afe5062d4e17c48cf0756699a7522fbfd1b034bfb1eb2b636d34163 go1.22.10.netbsd-arm64.tar.gz +9d5e08939b95dca7d102eae7a832813ee12337187786f548848b10503ddc80cc go1.22.10.netbsd-arm.tar.gz +5c4294fed0bf981f81b8be979e955516d0a78019990f2f7462246ef4bb3960e5 go1.22.10.openbsd-386.tar.gz +c72b4d4f4cedce007e4d52a0e496169b0739b3bbe7fa13bc1322fe3b79d55221 go1.22.10.openbsd-amd64.tar.gz +1a90e0fb2e6ca881b78692594f0e29834f738c593ea627a631f8e81cdddfdc32 go1.22.10.openbsd-arm64.tar.gz +f6153ebd844ccc21ec8420fecccf9214dc0a7a572b28234ec1e00e04b0f438bb go1.22.10.openbsd-arm.tar.gz +5fe1abee2805334404da9c4e2aedb93764b31d6de3fe9d3cb49a2fd844992f68 go1.22.10.openbsd-ppc64.tar.gz +834fe42edf63a67b7b6c57fcfd1e8b159842f3615bfbc975b5ceed994db7e834 go1.22.10.plan9-386.tar.gz +9fa2282fccb668ef9e3cdb1fa3f0128994224f95267f54640779049df3bb3a6c go1.22.10.plan9-amd64.tar.gz +d66acc86e00ead41966bb599c26fda983a686232040996a307262e71b3576c48 go1.22.10.plan9-arm.tar.gz +40779a36f3f78b2ac1f3da5e6336f4fa24996d2412d5ca5685077103c8d3bbe2 go1.22.10.solaris-amd64.tar.gz +974656452fd7d104f34ee6e8ac92bb7431af84a1ce55226d9af485cb9ec23dd5 go1.22.10.windows-arm64.zip +f62491c8bbc811ddbdd25ee34af5bbd200f3d6086c1a3ebb64e435ce3b346a97 go1.22.10.windows-arm64.msi +de1fb78d98ebd054a6fce5b71d1dcd1d49a68db75e0b6fd3d98ae5818d81fdba go1.22.10.windows-arm.zip +5523272bb157db06311a907289c92caf59347d4b6721d407486256e61c593dc9 go1.22.10.windows-arm.msi + +# version:golangci 1.63.4 +# https://github.com/golangci/golangci-lint/releases/ +# https://github.com/golangci/golangci-lint/releases/download/v1.63.4/ +878d017cc360e4fb19510d39852c8189852e3c48e7ce0337577df73507c97d68 golangci-lint-1.63.4-darwin-amd64.tar.gz +a2b630c2ac8466393f0ccbbede4462387b6c190697a70bc2298c6d2123f21bbf golangci-lint-1.63.4-darwin-arm64.tar.gz +8938b74aa92888e561a1c5a4c175110b92f84e7d24733703e6d9ebc39e9cd5f8 golangci-lint-1.63.4-freebsd-386.tar.gz +054903339d620df2e760b978920100986e3b03bcb058f669d520a71dac9c34ed golangci-lint-1.63.4-freebsd-amd64.tar.gz +a19d499f961a02608348e8b626537a88edfaab6e1b6534f1eff742b5d6d750e4 golangci-lint-1.63.4-freebsd-armv6.tar.gz +00d616f0fb275b780ce4d26604bdd7fdbfe6bc9c63acd5a0b31498e1f7511108 golangci-lint-1.63.4-freebsd-armv7.tar.gz +d453688e0eabded3c1a97ff5a2777bb0df5a18851efdaaaf6b472e3e5713c33e golangci-lint-1.63.4-illumos-amd64.tar.gz +6b1bec847fc9f347d53712d05606a49d55d0e3b5c1bacadfed2393f3503de0e9 golangci-lint-1.63.4-linux-386.tar.gz +01abb14a4df47b5ca585eff3c34b105023cba92ec34ff17212dbb83855581690 golangci-lint-1.63.4-linux-amd64.tar.gz +51f0c79d19a92353e0465fb30a4901a0644a975d34e6f399ad2eebc0160bbb24 golangci-lint-1.63.4-linux-arm64.tar.gz +8d0a43f41e8424fbae10f7aa2dc29999f98112817c6dba63d7dc76832940a673 golangci-lint-1.63.4-linux-armv6.tar.gz +1045a047b31e9302c9160c7b0f199f4ac1bd02a1b221a2d9521bd3507f0cf671 golangci-lint-1.63.4-linux-armv7.tar.gz +933fe10ab50ce3bb0806e15a4ae69fe20f0549abf91dea0161236000ca706e67 golangci-lint-1.63.4-linux-loong64.tar.gz +45798630cbad5642862766051199fa862ef3c33d569cab12f01cac4f68e2ddd5 golangci-lint-1.63.4-linux-mips64.tar.gz +86ae25335ddb24975d2c915c1af0c7fad70dce99d0b4614fa4bee392de714aa2 golangci-lint-1.63.4-linux-mips64le.tar.gz +33dabd11aaba4b602938da98bcf49aabab55019557e0115cdc3dbcc3009768fa golangci-lint-1.63.4-linux-ppc64le.tar.gz +4e7a81230a663bcdf30bba5689ce96040abc76994dbc2003dce32c8dca8c06f3 golangci-lint-1.63.4-linux-riscv64.tar.gz +21370b49c7c47f4d9b8f982c952f940b01e65710174c3b4dad7b6452d58f92ec golangci-lint-1.63.4-linux-s390x.tar.gz +255866a6464c7e11bb7edd8e6e6ad54f11e1f01b82ba9ca229698ac788cd9724 golangci-lint-1.63.4-netbsd-386.tar.gz +2798c040ac658bda97224f204795199c81ac97bb207b21c02b664aaed380d5d2 golangci-lint-1.63.4-netbsd-amd64.tar.gz +b910eecffd0064103837e7e1abe870deb8ade22331e6dffe319f430d49399c8e golangci-lint-1.63.4-netbsd-arm64.tar.gz +df2693ef37147b457c3e2089614537dd2ae2e18e53641e756a5b404f4c72d3fa golangci-lint-1.63.4-netbsd-armv6.tar.gz +a28a533366974bd7834c4516cd6075bff3419a508d1ed7aa63ae8182768b352e golangci-lint-1.63.4-netbsd-armv7.tar.gz +368932775fb5c620b324dabf018155f3365f5e33c5af5b26e9321db373f96eea golangci-lint-1.63.4-windows-386.zip +184d13c2b8f5441576bec2a0d8ba7b2d45445595cf796b879a73bcc98c39f8c1 golangci-lint-1.63.4-windows-amd64.zip +4fabf175d5b05ef0858ded49527948eebac50e9093814979fd84555a75fb80a6 golangci-lint-1.63.4-windows-arm64.zip +e92be3f3ff30d4a849fb4b9a4c8d56837dee45269cb405a3ecad52fa034c781b golangci-lint-1.63.4-windows-armv6.zip +c71d348653b8f7fbb109bb10c1a481722bc6b0b2b6e731b897f99ac869f7653e golangci-lint-1.63.4-windows-armv7.zip + +# This is the builder on PPA that will build Go itself (inception-y), don't modify! +# +# This version is fine to be old and full of security holes, we just use it +# to build the latest Go. Don't change it. +# +# version:ppa-builder-1 1.19.6 +# https://go.dev/dl/ +d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz + +# version:ppa-builder-2 1.21.9 +# https://go.dev/dl/ +58f0c5ced45a0012bce2ff7a9df03e128abcc8818ebabe5027bb92bafe20e421 go1.21.9.src.tar.gz + +# version:protoc 27.1 +# https://github.com/protocolbuffers/protobuf/releases/ +# https://github.com/protocolbuffers/protobuf/releases/download/v27.1/ +8809c2ec85368c6b6e9af161b6771a153aa92670a24adbe46dd34fa02a04df2f protoc-27.1-linux-aarch_64.zip +5d21979a6d27475e810b76b88863d1e784fa01ffb15e511a3ec5bd1924d89426 protoc-27.1-linux-ppcle_64.zip +84d8852750ed186dc4a057a1a86bcac409be5362d6af04770f42367fee6b7bc1 protoc-27.1-linux-s390_64.zip +2f028796ff5741691650e0eea290e61ff2f1c0d87f8d31fe45ef47fd967cef0c protoc-27.1-linux-x86_32.zip +8970e3d8bbd67d53768fe8c2e3971bdd71e51cfe2001ca06dacad17258a7dae3 protoc-27.1-linux-x86_64.zip +03b7af1bf469e7285dc51976ee5fa99412704dbd1c017105114852a37b165c12 protoc-27.1-osx-aarch_64.zip +f14d3973cf13283d07c520ed6f4c12405ad41b9efd18089a1c74897037d742b5 protoc-27.1-osx-universal_binary.zip +8520d944f3a3890fa296a3b3b0d4bb18337337e2526bbbf1b507eeea3c2a1ec4 protoc-27.1-osx-x86_64.zip +6263718ff96547b8392a079f6fdf02a4156f2e8d13cd51649a0d03fb7afa2de8 protoc-27.1-win32.zip +da531c51ccd1290d8d34821f0ce4e219c7fbaa6f9825f5a3fb092a9d03fe6206 protoc-27.1-win64.zip + +# version:protoc-gen-go 1.34.2 +# https://github.com/protocolbuffers/protobuf-go/releases/ +# https://github.com/protocolbuffers/protobuf-go/releases/download/v1.34.2/ +9b48d8f90add02e8e94e14962fed74e7ce2b2d6bda4dd42f1f4fbccf0f766f1a protoc-gen-go.v1.34.2.darwin.amd64.tar.gz +17aca7f948dbb624049030cf841e35895cf34183ba006e721247fdeb95ff2780 protoc-gen-go.v1.34.2.darwin.arm64.tar.gz +a191849433fd489f1d44f37788d762658f3f5fb225f3a85d4ce6ba32666703ed protoc-gen-go.v1.34.2.linux.386.tar.gz +b87bc134dee55576a842141bf0ed27761c635d746780fce5dee038c6dd16554f protoc-gen-go.v1.34.2.linux.amd64.tar.gz +63d400167e75ab9f6690688f6fdc6a9455aa20bc1faa71e32149dbd322f7f198 protoc-gen-go.v1.34.2.linux.arm64.tar.gz +56e7675816db6e62be4f833a51544d5716b8420c462515579e05ca8444ab06ed protoc-gen-go.v1.34.2.windows.386.zip +abafd39612177dd4e9a65207cadd5374a9352d8611e8e040f8462fcfa3010daf protoc-gen-go.v1.34.2.windows.amd64.zip diff --git a/build/ci.go b/build/ci.go index ff3ea89d10f6..3bdb52a7a32e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -246,37 +246,52 @@ func doTest(cmdline []string) { build.MustRun(gotest) } +// doLint runs golangci-lint on requested packages. func doLint(cmdline []string) { + var ( + cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.") + ) flag.CommandLine.Parse(cmdline) - packages := []string{"./..."} if len(flag.CommandLine.Args()) > 0 { packages = flag.CommandLine.Args() } - // Get golangci-lint and install all supported linters - build.MustRun(goTool("get", "github.com/golangci/golangci-lint/cmd/golangci-lint@v1.18.0")) - - // Run fast linters batched together - configs := []string{ - "run", - "--disable-all", - "--enable=vet", - "--enable=gofmt", - "--enable=misspell", - "--enable=goconst", - "--min-occurrences=6", // for goconst + + linter := downloadLinter(*cachedir) + lflags := []string{"run", "--config", ".golangci.yml"} + build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) + fmt.Println("You have achieved perfection.") +} + +// downloadLinter downloads and unpacks golangci-lint. +func downloadLinter(cachedir string) string { + csdb := build.MustLoadChecksums("build/checksums.txt") + version, err := build.Version(csdb, "golangci") + if err != nil { + log.Fatal(err) } - build.MustRunCommand(filepath.Join(GOBIN, "golangci-lint"), append(configs, packages...)...) + arch := runtime.GOARCH + ext := ".tar.gz" - // Run slow linters one by one - for _, linter := range []string{"unconvert", "gosimple"} { - configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter} - build.MustRunCommand(filepath.Join(GOBIN, "golangci-lint"), append(configs, packages...)...) + if runtime.GOOS == "windows" { + ext = ".zip" + } + if arch == "arm" { + arch += "v" + os.Getenv("GOARM") + } + base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch) + url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s%s", version, base, ext) + archivePath := filepath.Join(cachedir, base+ext) + if err := csdb.DownloadFile(url, archivePath); err != nil { + log.Fatal(err) } + if err := build.ExtractArchive(archivePath, cachedir); err != nil { + log.Fatal(err) + } + return filepath.Join(cachedir, base, "golangci-lint") } // Cross compilation - func doXgo(cmdline []string) { var ( alltools = flag.Bool("alltools", false, `Flag whether we're building all known tools, or only on in particular`) diff --git a/go.mod b/go.mod index 3b58b4c90cdc..9b6faf749c1b 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/XinFinOrg/XDPoSChain go 1.22 +toolchain go1.22.0 + require ( github.com/VictoriaMetrics/fastcache v1.12.2 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 diff --git a/internal/build/archive.go b/internal/build/archive.go new file mode 100644 index 000000000000..645921c690a7 --- /dev/null +++ b/internal/build/archive.go @@ -0,0 +1,296 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package build + +import ( + "archive/tar" + "archive/zip" + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" +) + +type Archive interface { + // Directory adds a new directory entry to the archive and sets the + // directory for subsequent calls to Header. + Directory(name string) error + + // Header adds a new file to the archive. The file is added to the directory + // set by Directory. The content of the file must be written to the returned + // writer. + Header(os.FileInfo) (io.Writer, error) + + // Close flushes the archive and closes the underlying file. + Close() error +} + +func NewArchive(file *os.File) (Archive, string) { + switch { + case strings.HasSuffix(file.Name(), ".zip"): + return NewZipArchive(file), strings.TrimSuffix(file.Name(), ".zip") + case strings.HasSuffix(file.Name(), ".tar.gz"): + return NewTarballArchive(file), strings.TrimSuffix(file.Name(), ".tar.gz") + default: + return nil, "" + } +} + +// AddFile appends an existing file to an archive. +func AddFile(a Archive, file string) error { + fd, err := os.Open(file) + if err != nil { + return err + } + defer fd.Close() + fi, err := fd.Stat() + if err != nil { + return err + } + w, err := a.Header(fi) + if err != nil { + return err + } + if _, err := io.Copy(w, fd); err != nil { + return err + } + return nil +} + +// WriteArchive creates an archive containing the given files. +func WriteArchive(name string, files []string) (err error) { + archfd, err := os.Create(name) + if err != nil { + return err + } + + defer func() { + archfd.Close() + // Remove the half-written archive on failure. + if err != nil { + os.Remove(name) + } + }() + archive, basename := NewArchive(archfd) + if archive == nil { + return errors.New("unknown archive extension") + } + fmt.Println(name) + if err := archive.Directory(basename); err != nil { + return err + } + for _, file := range files { + fmt.Println(" +", filepath.Base(file)) + if err := AddFile(archive, file); err != nil { + return err + } + } + return archive.Close() +} + +type ZipArchive struct { + dir string + zipw *zip.Writer + file io.Closer +} + +func NewZipArchive(w io.WriteCloser) Archive { + return &ZipArchive{"", zip.NewWriter(w), w} +} + +func (a *ZipArchive) Directory(name string) error { + a.dir = name + "/" + return nil +} + +func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) { + head, err := zip.FileInfoHeader(fi) + if err != nil { + return nil, fmt.Errorf("can't make zip header: %v", err) + } + head.Name = a.dir + head.Name + head.Method = zip.Deflate + w, err := a.zipw.CreateHeader(head) + if err != nil { + return nil, fmt.Errorf("can't add zip header: %v", err) + } + return w, nil +} + +func (a *ZipArchive) Close() error { + if err := a.zipw.Close(); err != nil { + return err + } + return a.file.Close() +} + +type TarballArchive struct { + dir string + tarw *tar.Writer + gzw *gzip.Writer + file io.Closer +} + +func NewTarballArchive(w io.WriteCloser) Archive { + gzw := gzip.NewWriter(w) + tarw := tar.NewWriter(gzw) + return &TarballArchive{"", tarw, gzw, w} +} + +func (a *TarballArchive) Directory(name string) error { + a.dir = name + "/" + return a.tarw.WriteHeader(&tar.Header{ + Name: a.dir, + Mode: 0755, + Typeflag: tar.TypeDir, + ModTime: time.Now(), + }) +} + +func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) { + head, err := tar.FileInfoHeader(fi, "") + if err != nil { + return nil, fmt.Errorf("can't make tar header: %v", err) + } + head.Name = a.dir + head.Name + if err := a.tarw.WriteHeader(head); err != nil { + return nil, fmt.Errorf("can't add tar header: %v", err) + } + return a.tarw, nil +} + +func (a *TarballArchive) Close() error { + if err := a.tarw.Close(); err != nil { + return err + } + if err := a.gzw.Close(); err != nil { + return err + } + return a.file.Close() +} + +// ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory. +func ExtractArchive(archive string, dest string) error { + ar, err := os.Open(archive) + if err != nil { + return err + } + defer ar.Close() + + switch { + case strings.HasSuffix(archive, ".tar.gz"): + return extractTarball(ar, dest) + case strings.HasSuffix(archive, ".zip"): + return extractZip(ar, dest) + default: + return fmt.Errorf("unhandled archive type %s", archive) + } +} + +// extractTarball unpacks a .tar.gz file. +func extractTarball(ar io.Reader, dest string) error { + gzr, err := gzip.NewReader(ar) + if err != nil { + return err + } + defer gzr.Close() + + tr := tar.NewReader(gzr) + for { + // Move to the next file header. + header, err := tr.Next() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + // We only care about regular files, directory modes + // and special file types are not supported. + if header.Typeflag == tar.TypeReg { + armode := header.FileInfo().Mode() + err := extractFile(header.Name, armode, tr, dest) + if err != nil { + return fmt.Errorf("extract %s: %v", header.Name, err) + } + } + } +} + +// extractZip unpacks the given .zip file. +func extractZip(ar *os.File, dest string) error { + info, err := ar.Stat() + if err != nil { + return err + } + zr, err := zip.NewReader(ar, info.Size()) + if err != nil { + return err + } + + for _, zf := range zr.File { + if !zf.Mode().IsRegular() { + continue + } + + data, err := zf.Open() + if err != nil { + return err + } + err = extractFile(zf.Name, zf.Mode(), data, dest) + data.Close() + if err != nil { + return fmt.Errorf("extract %s: %v", zf.Name, err) + } + } + return nil +} + +// extractFile extracts a single file from an archive. +func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) error { + // Check that path is inside destination directory. + target := filepath.Join(dest, filepath.FromSlash(arpath)) + if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("path %q escapes archive destination", target) + } + + // Remove the preivously-extracted file if it exists + if err := os.RemoveAll(target); err != nil { + return err + } + + // Recreate the destination directory + if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { + return err + } + + // Copy file data. + file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, armode) + if err != nil { + return err + } + if _, err = io.Copy(file, data); err != nil { + file.Close() + os.Remove(target) + return err + } + return file.Close() +} diff --git a/internal/build/download.go b/internal/build/download.go new file mode 100644 index 000000000000..50268227a501 --- /dev/null +++ b/internal/build/download.go @@ -0,0 +1,151 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package build + +import ( + "bufio" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" +) + +// ChecksumDB keeps file checksums. +type ChecksumDB struct { + allChecksums []string +} + +// MustLoadChecksums loads a file containing checksums. +func MustLoadChecksums(file string) *ChecksumDB { + content, err := os.ReadFile(file) + if err != nil { + log.Fatal("can't load checksum file: " + err.Error()) + } + return &ChecksumDB{strings.Split(strings.ReplaceAll(string(content), "\r\n", "\n"), "\n")} +} + +// Verify checks whether the given file is valid according to the checksum database. +func (db *ChecksumDB) Verify(path string) error { + fd, err := os.Open(path) + if err != nil { + return err + } + defer fd.Close() + + h := sha256.New() + if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil { + return err + } + fileHash := hex.EncodeToString(h.Sum(nil)) + if !db.findHash(filepath.Base(path), fileHash) { + return fmt.Errorf("invalid file hash: %s %s", fileHash, filepath.Base(path)) + } + return nil +} + +func (db *ChecksumDB) findHash(basename, hash string) bool { + want := hash + " " + basename + for _, line := range db.allChecksums { + if strings.TrimSpace(line) == want { + return true + } + } + return false +} + +// DownloadFile downloads a file and verifies its checksum. +func (db *ChecksumDB) DownloadFile(url, dstPath string) error { + if err := db.Verify(dstPath); err == nil { + fmt.Printf("%s is up-to-date\n", dstPath) + return nil + } + fmt.Printf("%s is stale\n", dstPath) + fmt.Printf("downloading from %s\n", url) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("download error: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("download error: status %d", resp.StatusCode) + } + if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil { + return err + } + fd, err := os.OpenFile(dstPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return err + } + dst := newDownloadWriter(fd, resp.ContentLength) + _, err = io.Copy(dst, resp.Body) + dst.Close() + if err != nil { + return err + } + return db.Verify(dstPath) +} + +type downloadWriter struct { + file *os.File + dstBuf *bufio.Writer + size int64 + written int64 + lastpct int64 +} + +func newDownloadWriter(dst *os.File, size int64) *downloadWriter { + return &downloadWriter{ + file: dst, + dstBuf: bufio.NewWriter(dst), + size: size, + } +} + +func (w *downloadWriter) Write(buf []byte) (int, error) { + n, err := w.dstBuf.Write(buf) + + // Report progress. + w.written += int64(n) + pct := w.written * 10 / w.size * 10 + if pct != w.lastpct { + if w.lastpct != 0 { + fmt.Print("...") + } + fmt.Print(pct, "%") + w.lastpct = pct + } + return n, err +} + +func (w *downloadWriter) Close() error { + if w.lastpct > 0 { + fmt.Println() // Finish the progress line. + } + flushErr := w.dstBuf.Flush() + closeErr := w.file.Close() + if flushErr != nil { + return flushErr + } + return closeErr +} diff --git a/internal/build/gotool.go b/internal/build/gotool.go new file mode 100644 index 000000000000..2a474604183f --- /dev/null +++ b/internal/build/gotool.go @@ -0,0 +1,180 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package build + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +type GoToolchain struct { + Root string // GOROOT + + // Cross-compilation variables. These are set when running the go tool. + GOARCH string + GOOS string + CC string +} + +// Go creates an invocation of the go command. +func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd { + tool := g.goTool(command, args...) + + // Configure environment for cross build. + if g.GOARCH != "" && g.GOARCH != runtime.GOARCH { + tool.Env = append(tool.Env, "CGO_ENABLED=1") + tool.Env = append(tool.Env, "GOARCH="+g.GOARCH) + } + if g.GOOS != "" && g.GOOS != runtime.GOOS { + tool.Env = append(tool.Env, "GOOS="+g.GOOS) + } + // Configure C compiler. + if g.CC != "" { + tool.Env = append(tool.Env, "CC="+g.CC) + } else if os.Getenv("CC") != "" { + tool.Env = append(tool.Env, "CC="+os.Getenv("CC")) + } + // CKZG by default is not portable, append the necessary build flags to make + // it not rely on modern CPU instructions and enable linking against. + tool.Env = append(tool.Env, "CGO_CFLAGS=-O2 -g -D__BLST_PORTABLE__") + + return tool +} + +func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd { + if g.Root == "" { + g.Root = runtime.GOROOT() + } + tool := exec.Command(filepath.Join(g.Root, "bin", "go"), command) // nolint: gosec + tool.Args = append(tool.Args, args...) + tool.Env = append(tool.Env, "GOROOT="+g.Root) + + // Forward environment variables to the tool, but skip compiler target settings. + // TODO: what about GOARM? + skip := map[string]struct{}{"GOROOT": {}, "GOARCH": {}, "GOOS": {}, "GOBIN": {}, "CC": {}} + for _, e := range os.Environ() { + if i := strings.IndexByte(e, '='); i >= 0 { + if _, ok := skip[e[:i]]; ok { + continue + } + } + tool.Env = append(tool.Env, e) + } + return tool +} + +// DownloadGo downloads the Go binary distribution and unpacks it into a temporary +// directory. It returns the GOROOT of the unpacked toolchain. +func DownloadGo(csdb *ChecksumDB) string { + version, err := Version(csdb, "golang") + if err != nil { + log.Fatal(err) + } + // Shortcut: if the Go version that runs this script matches the + // requested version exactly, there is no need to download anything. + activeGo := strings.TrimPrefix(runtime.Version(), "go") + if activeGo == version { + log.Printf("-dlgo version matches active Go version %s, skipping download.", activeGo) + return runtime.GOROOT() + } + + ucache, err := os.UserCacheDir() + if err != nil { + log.Fatal(err) + } + + // For Arm architecture, GOARCH includes ISA version. + os := runtime.GOOS + arch := runtime.GOARCH + if arch == "arm" { + arch = "armv6l" + } + file := fmt.Sprintf("go%s.%s-%s", version, os, arch) + if os == "windows" { + file += ".zip" + } else { + file += ".tar.gz" + } + url := "https://golang.org/dl/" + file + dst := filepath.Join(ucache, file) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Fatal(err) + } + + godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", version, os, arch)) + if err := ExtractArchive(dst, godir); err != nil { + log.Fatal(err) + } + goroot, err := filepath.Abs(filepath.Join(godir, "go")) + if err != nil { + log.Fatal(err) + } + return goroot +} + +// Version returns the versions defined in the checksumdb. +func Version(csdb *ChecksumDB, version string) (string, error) { + for _, l := range csdb.allChecksums { + if !strings.HasPrefix(l, "# version:") { + continue + } + v := strings.Split(l, ":")[1] + parts := strings.Split(v, " ") + if len(parts) != 2 { + log.Print("Erroneous version-string", "v", l) + continue + } + if parts[0] == version { + return parts[1], nil + } + } + return "", fmt.Errorf("no version found for '%v'", version) +} + +// DownloadAndVerifyChecksums downloads all files and checks that they match +// the checksum given in checksums.txt. +// This task can be used to sanity-check new checksums. +func DownloadAndVerifyChecksums(csdb *ChecksumDB) { + var ( + base = "" + ucache = os.TempDir() + ) + for _, l := range csdb.allChecksums { + if strings.HasPrefix(l, "# https://") { + base = l[2:] + continue + } + if strings.HasPrefix(l, "#") { + continue + } + hashFile := strings.Split(l, " ") + if len(hashFile) != 2 { + continue + } + file := hashFile[1] + url := base + file + dst := filepath.Join(ucache, file) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Print(err) + } + } +} diff --git a/internal/build/util.go b/internal/build/util.go index 3fcb411c70de..42f57a468181 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -26,7 +26,9 @@ import ( "path" "path/filepath" "runtime" + "strconv" "strings" + "time" ) var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands") @@ -34,7 +36,7 @@ var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands") // MustRun executes the given command and exits the host process for // any error. func MustRun(cmd *exec.Cmd) { - fmt.Println(">>>", strings.Join(cmd.Args, " ")) + fmt.Println(">>>", printArgs(cmd.Args)) if !*DryRunFlag { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout @@ -44,10 +46,45 @@ func MustRun(cmd *exec.Cmd) { } } +func printArgs(args []string) string { + var s strings.Builder + for i, arg := range args { + if i > 0 { + s.WriteByte(' ') + } + if strings.IndexByte(arg, ' ') >= 0 { + arg = strconv.QuoteToASCII(arg) + } + s.WriteString(arg) + } + return s.String() +} + func MustRunCommand(cmd string, args ...string) { MustRun(exec.Command(cmd, args...)) } +// MustRunCommandWithOutput runs the given command, and ensures that some output will be +// printed while it runs. This is useful for CI builds where the process will be stopped +// when there is no output. +func MustRunCommandWithOutput(cmd string, args ...string) { + interval := time.NewTicker(time.Minute) + done := make(chan struct{}) + defer interval.Stop() + defer close(done) + go func() { + for { + select { + case <-interval.C: + fmt.Printf("Waiting for command %q\n", cmd) + case <-done: + return + } + } + }() + MustRun(exec.Command(cmd, args...)) +} + // GOPATH returns the value that the GOPATH environment // variable should be set to. func GOPATH() string {