├── go.sum ├── .gitignore ├── go.mod ├── main.go ├── .github ├── dependabot.yml └── workflows │ └── release.yaml ├── README.md └── .goreleaser.yaml /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist/ 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goreleaser/goreleaser-zig-cross-compilation 2 | 3 | go 1.25.4 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #include 4 | // void callC(){ 5 | // printf("Calling C code from Golang!\n"); 6 | // } 7 | import "C" 8 | 9 | import "fmt" 10 | 11 | func main() { 12 | fmt.Println("Go is about to call C!") 13 | C.callC() 14 | fmt.Println("C function already called successfully!") 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "monday" 8 | time: "05:00" 9 | timezone: "America/New_York" 10 | labels: 11 | - "dependencies" 12 | commit-message: 13 | prefix: "chore" 14 | include: "scope" 15 | groups: 16 | go: 17 | patterns: 18 | - "*" 19 | - package-ecosystem: "github-actions" 20 | directory: "/" 21 | schedule: 22 | interval: "weekly" 23 | day: "monday" 24 | time: "05:00" 25 | timezone: "America/New_York" 26 | labels: 27 | - "dependencies" 28 | commit-message: 29 | prefix: "ci" 30 | include: "scope" 31 | groups: 32 | actions: 33 | patterns: 34 | - "*" 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | # run only on tags 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | permissions: 10 | contents: write # needed to write releases 11 | 12 | jobs: 13 | release: 14 | runs-on: macos-latest 15 | steps: 16 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 17 | with: 18 | fetch-depth: 0 19 | - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 20 | with: 21 | go-version-file: go.mod 22 | - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 23 | - name: Set output 24 | id: macos_sdk 25 | run: echo "path=$(xcrun --show-sdk-path)" >> $GITHUB_OUTPUT 26 | - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 27 | with: 28 | version: latest 29 | args: release --clean 30 | env: 31 | SDK_PATH: ${{ steps.macos_sdk.outputs.path }} 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross-compile Go application for major platforms with Zig and GoReleaser with CGO 2 | 3 | Cross-compilation in Go refers to building a Go program on one platform and for another platform. It allows you to create binaries that can be run on systems with different operating systems or architectures from the one on which the program was built. To do that, you just need to specify `GOOS` and `GOARCH` when running go build. 4 | 5 | Unfortunately for projects that uses CGO dependencies, things can be harder. Depending on the target architecture it requires the installation of a C compiler like `gcc`, `clang` or `x86_64-w64-mingw64-gcc` and configuring additional environment variables like CC along with the CGO_ENABLED=1 one. 6 | 7 | Cross-compiling with cgo involves building a Go program that uses C code and compiling it for a different target platform than the one on which the program is built. 8 | 9 | The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling. You can control this by setting the CGO_ENABLED environment variable when running the go tool: set it to 1 to enable the use of cgo, and to 0 to disable it. The go tool will set the 10 | build constraint "cgo" if cgo is enabled. 11 | 12 | When cross-compiling, you must specify a C cross-compiler for cgo to use. You can do this by setting the CC_FOR_TARGET environment variable when building the toolchain using make.bash, or by setting the CC environment variable any time you run the go tool. The CXX_FOR_TARGET and CXX environment variables work in a similar way for C++ code. 13 | > 14 | 15 | ## Zig Cross-compilation 16 | 17 | Zig is a programming language that aims to be a better alternative to C, offering a simpler syntax, memory safety, and more expressive error handling. It also includes built-in cross-compilation support. 18 | 19 | Zig is a full-fledged C/C++ cross-compiler that leverages LLVM. The crucial detail here is what Zig includes to make cross-compilation possible: Zig bundles standard libraries for all major platforms (GNU libc, musl libc, ...), an advanced artifact caching system, and it has a flag-compatible interface for both clang and gcc. 20 | > 21 | 22 | When cross-compiling Zig code, you can use the zig cc and zig c++ commands to compile C and C++ code, respectively. These commands are wrappers around the appropriate compiler for the target platform, and they handle passing the correct flags and options to the underlying compiler. 23 | 24 | If you want to cross-compile for `x86_64` Linux, for example, all you need to do is add: 25 | 26 | * `CC="zig cc -target x86_64-linux"` 27 | * `CXX="zig c++ -target x86_64-linux"` 28 | 29 | To the list of env variables when invoking `go build`. Example: 30 | 31 | ```shell 32 | CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux" CXX="zig c++ -target x86_64-linux" go build 33 | ``` 34 | 35 | ## Local builds (macOS) 36 | 37 | Set SDK_PATH when building locally for darwin targets: 38 | 39 | ```shell 40 | SDK_PATH=$(xcrun --show-sdk-path) goreleaser release --clean --snapshot 41 | ``` 42 | 43 | Or with go build: 44 | 45 | ```shell 46 | SDK_PATH=$(xcrun --show-sdk-path) CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 CC="clang -arch arm64 -isysroot $SDK_PATH" CXX="clang++ -arch arm64 -isysroot $SDK_PATH" go build 47 | ``` 48 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 2 | project_name: goreleaser-zig-cross-compilation 3 | # This is an example .goreleaser.yml file with some sensible defaults. 4 | # Make sure to check the documentation at https://goreleaser.com 5 | before: 6 | hooks: 7 | # You may remove this if you don't use go modules. 8 | - go mod tidy 9 | builds: 10 | - goos: 11 | - darwin 12 | - linux 13 | - windows 14 | goarch: 15 | - amd64 16 | - arm64 17 | ldflags: 18 | - -s -w 19 | flags: 20 | - -trimpath 21 | - >- 22 | {{- if eq .Os "linux" }}-buildmode=pie{{- end }} 23 | env: 24 | - CGO_ENABLED=1 25 | - >- 26 | {{- if eq .Os "darwin" }}SDKROOT={{ .Env.SDK_PATH }}{{- end }} 27 | - >- 28 | {{- if eq .Os "darwin" }}MACOSX_DEPLOYMENT_TARGET=11.0{{- end }} 29 | - >- 30 | {{- if eq .Os "darwin" }}CGO_CFLAGS=-isysroot {{ .Env.SDK_PATH }} -mmacosx-version-min=11.0{{- end }} 31 | - >- 32 | {{- if eq .Os "darwin" }}CGO_CXXFLAGS=-isysroot {{ .Env.SDK_PATH }} -mmacosx-version-min=11.0{{- end }} 33 | - >- 34 | {{- if eq .Os "darwin" }}CGO_LDFLAGS=-isysroot {{ .Env.SDK_PATH }}{{- end }} 35 | - >- 36 | {{- if eq .Os "darwin" }} 37 | {{- if eq .Arch "amd64"}}CC=clang -arch x86_64 -isysroot {{ .Env.SDK_PATH }}{{- end }} 38 | {{- if eq .Arch "arm64"}}CC=clang -arch arm64 -isysroot {{ .Env.SDK_PATH }}{{- end }} 39 | {{- else if eq .Os "linux" }} 40 | {{- if eq .Arch "amd64" }}CC=zig cc -target x86_64-linux-musl -lc{{- end }} 41 | {{- if eq .Arch "arm64"}}CC=zig cc -target aarch64-linux-musl -lc{{- end }} 42 | {{- else if eq .Os "windows" }} 43 | {{- if eq .Arch "amd64" }}CC=zig cc -target x86_64-windows-gnu -lc{{- end }} 44 | {{- if eq .Arch "arm64"}}CC=zig cc -target aarch64-windows-gnu -lc{{- end }} 45 | {{- end }} 46 | - >- 47 | {{- if eq .Os "darwin" }} 48 | {{- if eq .Arch "amd64"}}CXX=clang++ -arch x86_64 -isysroot {{ .Env.SDK_PATH }}{{- end }} 49 | {{- if eq .Arch "arm64"}}CXX=clang++ -arch arm64 -isysroot {{ .Env.SDK_PATH }}{{- end }} 50 | {{- else if eq .Os "linux" }} 51 | {{- if eq .Arch "amd64" }}CXX=zig c++ -target x86_64-linux-musl -lc{{- end }} 52 | {{- if eq .Arch "arm64"}}CXX=zig c++ -target aarch64-linux-musl -lc{{- end }} 53 | {{- else if eq .Os "windows" }} 54 | {{- if eq .Arch "amd64" }}CXX=zig c++ -target x86_64-windows-gnu -lc{{- end }} 55 | {{- if eq .Arch "arm64"}}CXX=zig c++ -target aarch64-windows-gnu -lc{{- end }} 56 | {{- end }} 57 | 58 | archives: 59 | - formats: 60 | - tar.gz 61 | # this name template makes the OS and Arch compatible with the results of uname. 62 | name_template: >- 63 | {{ .ProjectName }}_ 64 | {{- title .Os }}_ 65 | {{- if eq .Arch "amd64" }}x86_64 66 | {{- else if eq .Arch "386" }}i386 67 | {{- else }}{{ .Arch }}{{ end }} 68 | {{- if .Arm }}v{{ .Arm }}{{ end }} 69 | # use zip for windows archives 70 | format_overrides: 71 | - goos: windows 72 | formats: 73 | - zip 74 | checksum: 75 | name_template: "checksums.txt" 76 | snapshot: 77 | version_template: "{{ incpatch .Version }}-next" 78 | changelog: 79 | sort: asc 80 | filters: 81 | exclude: 82 | - "^docs:" 83 | - "^test:" 84 | --------------------------------------------------------------------------------