Multiplatform Builds
Why Build for Multiple Platforms?
There are a number of use cases for building code for different platforms. Common examples include:
- Building Docker images for both ARM64 (Apple Silicon, AWS Graviton) and AMD64 architectures
- Creating binaries for Windows, macOS, and Linux
- Supporting different CPU architectures for embedded systems
- Ensuring your application works across cloud providers with different infrastructure
Grog provides several approaches to for handling multi-platform builds.
Approaches to Multi-platform Building
There are two primary strategies for building for multiple platforms:
- Cross-compilation: Create a single build target that outputs artifacts for multiple platforms
- Platform-specific targets: Create separate build targets for each platform and run them on appropriate hosts
Grog supports both approaches, with platform selectors providing special support for the second strategy.
Cross-compilation Approach
Cross-compilation involves building for a different platform than the one you’re running on. Many languages and build tools support this out-of-the-box.
Example: Go Cross-compilation
Go has excellent built-in cross-compilation support:
targets: - name: build_multi_platform command: | # Build for Linux AMD64 GOOS=linux GOARCH=amd64 go build -o dist/myapp-linux-amd64 ./cmd/myapp
# Build for macOS ARM64 GOOS=darwin GOARCH=arm64 go build -o dist/myapp-darwin-arm64 ./cmd/myapp
# Build for Windows AMD64 GOOS=windows GOARCH=amd64 go build -o dist/myapp-windows-amd64.exe ./cmd/myapp inputs: - cmd/**/*.go - internal/**/*.go - go.mod - go.sum outputs: - dist/myapp-linux-amd64 - dist/myapp-darwin-arm64 - dist/myapp-windows-amd64.exe
Example: Multi-platform Docker Images
For Docker images, you can use Docker’s BuildX feature:
targets: - name: build_multi_arch_image command: | # Set up Docker BuildX builder with multi-platform support docker buildx create --name multiplatform-builder --use
# Build and push multi-platform image docker buildx build --platform linux/amd64,linux/arm64 \ -t registry.example.com/myapp:latest \ --push . inputs: - Dockerfile - src/**/* outputs: - docker::registry.example.com/myapp:latest
Platform-specific Targets with Platform Selectors
Grog’s platform selectors allow you to define targets that only run on specific platforms. When a target doesn’t match the current host’s platform, Grog will skip it automatically.
Defining Platform Selectors
You can specify platform requirements using the platform
field:
targets: - name: build_linux_amd64 command: go build -o dist/myapp-linux-amd64 ./cmd/myapp platform: os: - linux arch: - amd64 inputs: - cmd/**/*.go - go.mod outputs: - dist/myapp-linux-amd64
- name: build_darwin_arm64 command: go build -o dist/myapp-darwin-arm64 ./cmd/myapp platform: os: - darwin arch: - arm64 inputs: - cmd/**/*.go - go.mod outputs: - dist/myapp-darwin-arm64
In this example:
build_linux_amd64
will only run on Linux AMD64 hostsbuild_darwin_arm64
will only run on macOS ARM64 hosts (like Apple Silicon Macs)
Platform Selector Behavior
When you run grog build
, Grog will:
- Check each target’s platform selector against the os and arch the grog binary was built for
- Skip targets that don’t match the current platform
- Build targets that do match the platform
This allows you to define all platform variants in a single build file, but only build the ones appropriate for the current environment.
Handling Dependencies with Platform Selectors
When a target depends on a platform-specific target, Grog enforces platform compatibility:
targets: - name: linux_binary command: go build -o dist/myapp-linux ./cmd/myapp platform: os: - linux outputs: - dist/myapp-linux
- name: package_linux dependencies: - :linux_binary command: tar -czf dist/myapp-linux.tar.gz dist/myapp-linux platform: os: - linux outputs: - dist/myapp-linux.tar.gz
If you try to build package_linux
on a non-Linux platform, Grog will fail with an error because the dependency linux_binary
cannot be built on the current platform.
Platform-agnostic Dependencies
Sometimes you need to create platform-agnostic targets that depend on platform-specific ones. For these cases, you can use conditional logic in your build commands:
targets: - name: test_all_platforms dependencies: - :build_linux_amd64 # Will be skipped on non-Linux/AMD64 platforms - :build_darwin_arm64 # Will be skipped on non-macOS/ARM64 platforms command: | # Run tests for whatever platform we're on if [ "$(uname)" == "Darwin" ]; then ./test_darwin.sh elif [ "$(uname)" == "Linux" ]; then ./test_linux.sh else echo "Unsupported platform for testing" exit 1 fi