Skip to content

Commit

Permalink
Tweak build scripts to enable cross-compiling
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mislav committed Apr 9, 2021
1 parent d78e215 commit d437206
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 24 deletions.
17 changes: 11 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 22 additions & 2 deletions docs/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,33 @@
```

#### 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.

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

#### 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/).
52 changes: 36 additions & 16 deletions script/build.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Build tasks for the GitHub CLI project.
//
// Usage: go run script/build.go [<task>]
// Usage: go run script/build.go [<tasks>...] [<env>...]
//
// Known tasks are:
//
Expand Down Expand Up @@ -65,32 +65,52 @@ 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 {
Expand Down

0 comments on commit d437206

Please sign in to comment.