From d43720620e13a682fe6a060b518073783f39db98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 8 Apr 2021 21:11:15 +0200 Subject: [PATCH] Tweak build scripts to enable cross-compiling The main build script for this project is `script/build.go` which implements Makefile-like building of the `gh` binary and associated man pages. Our Makefile defers to the Go script. However, when setting GOOS, GOARCH, and other environment variables to modify the target for the resulting binary, these environment variables would affect the execution of `build.go` as well, which was unintended. This tweaks our Makefile to reset variables like GOOS and GOARCH when building the `build.go` script itself, ensuring that the built script runs on the same platform, and adds the ability to pass environment variables as arguments to `go run script/build.go`. This allows the following usage on platforms without `make`: go run script/build.go GOOS=linux With this style of invocation, the GOOS setting does not actually affect `go run` itself; just the `go build` that is executed in a child process. --- Makefile | 17 ++++++++++------ docs/source.md | 24 ++++++++++++++++++++-- script/build.go | 54 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 3dfc08a0e..46d40a7a9 100644 --- a/Makefile +++ b/Makefile @@ -5,22 +5,27 @@ export CGO_CFLAGS CGO_LDFLAGS ?= $(filter -g -L% -l% -O%,${LDFLAGS}) export CGO_LDFLAGS +EXE = +ifeq ($(GOOS),windows) +EXE = .exe +endif + ## The following tasks delegate to `script/build.go` so they can be run cross-platform. -.PHONY: bin/gh -bin/gh: script/build - @script/build bin/gh +.PHONY: bin/gh$(EXE) +bin/gh$(EXE): script/build + @script/build $@ script/build: script/build.go - go build -o script/build script/build.go + GOOS= GOARCH= GOARM= GOFLAGS= CGO_ENABLED= go build -o $@ $< .PHONY: clean clean: script/build - @script/build clean + @script/build $@ .PHONY: manpages manpages: script/build - @script/build manpages + @script/build $@ # just a convenience task around `go test` .PHONY: test diff --git a/docs/source.md b/docs/source.md index 28c9a2312..17e6b0185 100644 --- a/docs/source.md +++ b/docs/source.md @@ -27,9 +27,9 @@ ``` #### Windows - ```sh + ```pwsh # build the `bin\gh.exe` binary - > go run script/build.go + > go run script\build.go ``` There is no install step available on Windows. @@ -37,3 +37,23 @@ #### Windows Run `bin\gh version` to check if it worked. + +## Cross-compiling binaries for different platforms + +You can use any platform with Go installed to build a binary that is intended for another platform +or CPU architecture. This is achieved by setting environment variables such as GOOS and GOARCH. + +For example, to compile the `gh` binary for the 32-bit Raspberry Pi OS: +```sh +# on a Unix-like system: +$ GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 make clean bin/gh +``` +```pwsh +# on Windows, pass environment variables as arguments to the build script: +> go run script\build.go clean bin\gh GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 +``` + +Run `go tool dist list` to list all supported values of GOOS/GOARCH. + +Tip: to reduce the size of the resulting binary, you can use `GO_LDFLAGS="-s -w"`. This omits +symbol tables used for debugging. See the list of [supported linker flags](https://golang.org/cmd/link/). diff --git a/script/build.go b/script/build.go index 3d26ca7cb..2c92a5f20 100644 --- a/script/build.go +++ b/script/build.go @@ -1,6 +1,6 @@ // Build tasks for the GitHub CLI project. // -// Usage: go run script/build.go [] +// Usage: go run script/build.go [...] [...] // // Known tasks are: // @@ -65,34 +65,54 @@ var tasks = map[string]func(string) error{ var self string func main() { - task := "bin/gh" - if runtime.GOOS == "windows" { - task = "bin\\gh.exe" + args := os.Args[:1] + for _, arg := range os.Args[1:] { + if idx := strings.IndexRune(arg, '='); idx >= 0 { + os.Setenv(arg[:idx], arg[idx+1:]) + } else { + args = append(args, arg) + } } - if len(os.Args) > 1 { - task = os.Args[1] + if len(args) < 2 { + if isWindowsTarget() { + args = append(args, filepath.Join("bin", "gh.exe")) + } else { + args = append(args, "bin/gh") + } } - self = filepath.Base(os.Args[0]) + self = filepath.Base(args[0]) if self == "build" { self = "build.go" } - t := tasks[normalizeTask(task)] - if t == nil { - fmt.Fprintf(os.Stderr, "Don't know how to build task `%s`.\n", task) - os.Exit(1) - } + for _, task := range args[1:] { + t := tasks[normalizeTask(task)] + if t == nil { + fmt.Fprintf(os.Stderr, "Don't know how to build task `%s`.\n", task) + os.Exit(1) + } - err := t(task) - if err != nil { - fmt.Fprintln(os.Stderr, err) - fmt.Fprintf(os.Stderr, "%s: building task `%s` failed.\n", self, task) - os.Exit(1) + err := t(task) + if err != nil { + fmt.Fprintln(os.Stderr, err) + fmt.Fprintf(os.Stderr, "%s: building task `%s` failed.\n", self, task) + os.Exit(1) + } } } +func isWindowsTarget() bool { + if os.Getenv("GOOS") == "windows" { + return true + } + if runtime.GOOS == "windows" { + return true + } + return false +} + func version() string { if versionEnv := os.Getenv("GH_VERSION"); versionEnv != "" { return versionEnv