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