Skip to content

Commit d437206

Browse files
committed
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.
1 parent d78e215 commit d437206

File tree

3 files changed

+69
-24
lines changed

3 files changed

+69
-24
lines changed

Makefile

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@ export CGO_CFLAGS
55
CGO_LDFLAGS ?= $(filter -g -L% -l% -O%,${LDFLAGS})
66
export CGO_LDFLAGS
77

8+
EXE =
9+
ifeq ($(GOOS),windows)
10+
EXE = .exe
11+
endif
12+
813
## The following tasks delegate to `script/build.go` so they can be run cross-platform.
914

10-
.PHONY: bin/gh
11-
bin/gh: script/build
12-
@script/build bin/gh
15+
.PHONY: bin/gh$(EXE)
16+
bin/gh$(EXE): script/build
17+
@script/build $@
1318

1419
script/build: script/build.go
15-
go build -o script/build script/build.go
20+
GOOS= GOARCH= GOARM= GOFLAGS= CGO_ENABLED= go build -o $@ $<
1621

1722
.PHONY: clean
1823
clean: script/build
19-
@script/build clean
24+
@script/build $@
2025

2126
.PHONY: manpages
2227
manpages: script/build
23-
@script/build manpages
28+
@script/build $@
2429

2530
# just a convenience task around `go test`
2631
.PHONY: test

docs/source.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,33 @@
2727
```
2828

2929
#### Windows
30-
```sh
30+
```pwsh
3131
# build the `bin\gh.exe` binary
32-
> go run script/build.go
32+
> go run script\build.go
3333
```
3434
There is no install step available on Windows.
3535

3636
3. Run `gh version` to check if it worked.
3737

3838
#### Windows
3939
Run `bin\gh version` to check if it worked.
40+
41+
## Cross-compiling binaries for different platforms
42+
43+
You can use any platform with Go installed to build a binary that is intended for another platform
44+
or CPU architecture. This is achieved by setting environment variables such as GOOS and GOARCH.
45+
46+
For example, to compile the `gh` binary for the 32-bit Raspberry Pi OS:
47+
```sh
48+
# on a Unix-like system:
49+
$ GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 make clean bin/gh
50+
```
51+
```pwsh
52+
# on Windows, pass environment variables as arguments to the build script:
53+
> go run script\build.go clean bin\gh GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0
54+
```
55+
56+
Run `go tool dist list` to list all supported values of GOOS/GOARCH.
57+
58+
Tip: to reduce the size of the resulting binary, you can use `GO_LDFLAGS="-s -w"`. This omits
59+
symbol tables used for debugging. See the list of [supported linker flags](https://golang.org/cmd/link/).

script/build.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Build tasks for the GitHub CLI project.
22
//
3-
// Usage: go run script/build.go [<task>]
3+
// Usage: go run script/build.go [<tasks>...] [<env>...]
44
//
55
// Known tasks are:
66
//
@@ -65,32 +65,52 @@ var tasks = map[string]func(string) error{
6565
var self string
6666

6767
func main() {
68-
task := "bin/gh"
69-
if runtime.GOOS == "windows" {
70-
task = "bin\\gh.exe"
68+
args := os.Args[:1]
69+
for _, arg := range os.Args[1:] {
70+
if idx := strings.IndexRune(arg, '='); idx >= 0 {
71+
os.Setenv(arg[:idx], arg[idx+1:])
72+
} else {
73+
args = append(args, arg)
74+
}
7175
}
7276

73-
if len(os.Args) > 1 {
74-
task = os.Args[1]
77+
if len(args) < 2 {
78+
if isWindowsTarget() {
79+
args = append(args, filepath.Join("bin", "gh.exe"))
80+
} else {
81+
args = append(args, "bin/gh")
82+
}
7583
}
7684

77-
self = filepath.Base(os.Args[0])
85+
self = filepath.Base(args[0])
7886
if self == "build" {
7987
self = "build.go"
8088
}
8189

82-
t := tasks[normalizeTask(task)]
83-
if t == nil {
84-
fmt.Fprintf(os.Stderr, "Don't know how to build task `%s`.\n", task)
85-
os.Exit(1)
90+
for _, task := range args[1:] {
91+
t := tasks[normalizeTask(task)]
92+
if t == nil {
93+
fmt.Fprintf(os.Stderr, "Don't know how to build task `%s`.\n", task)
94+
os.Exit(1)
95+
}
96+
97+
err := t(task)
98+
if err != nil {
99+
fmt.Fprintln(os.Stderr, err)
100+
fmt.Fprintf(os.Stderr, "%s: building task `%s` failed.\n", self, task)
101+
os.Exit(1)
102+
}
86103
}
104+
}
87105

88-
err := t(task)
89-
if err != nil {
90-
fmt.Fprintln(os.Stderr, err)
91-
fmt.Fprintf(os.Stderr, "%s: building task `%s` failed.\n", self, task)
92-
os.Exit(1)
106+
func isWindowsTarget() bool {
107+
if os.Getenv("GOOS") == "windows" {
108+
return true
109+
}
110+
if runtime.GOOS == "windows" {
111+
return true
93112
}
113+
return false
94114
}
95115

96116
func version() string {

0 commit comments

Comments
 (0)