Skip to content

Commit dd15e97

Browse files
e2bclaude
authored andcommitted
feat: ARM64 architecture detection and build system
- Add TargetArch() utility for runtime architecture resolution with TARGET_ARCH env var override and alias normalization (x86_64↔amd64, aarch64↔arm64) - Add BUILD_ARCH/BUILD_PLATFORM variables to api, client-proxy, envd, and orchestrator Makefiles (defaults to host GOARCH) - Add fetch-busybox target for ARM64 busybox binary swap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 58c9147 commit dd15e97

6 files changed

Lines changed: 160 additions & 12 deletions

File tree

packages/api/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ PREFIX := $(strip $(subst ",,$(PREFIX)))
66
HOSTNAME := $(shell hostname 2> /dev/null || hostnamectl hostname 2> /dev/null)
77
$(if $(HOSTNAME),,$(error Failed to determine hostname: both 'hostname' and 'hostnamectl' failed))
88

9+
# Architecture for builds. Defaults to local arch; override for cross-compilation
10+
# (e.g., BUILD_ARCH=amd64 make build-and-upload from an ARM64 host).
11+
BUILD_ARCH ?= $(shell go env GOARCH)
12+
# Docker platform string. Override for multi-arch builds:
13+
# BUILD_PLATFORM=linux/amd64,linux/arm64 make build-and-upload
14+
BUILD_PLATFORM ?= linux/$(BUILD_ARCH)
15+
916
expectedMigration := $(shell ./../../scripts/get-latest-migration.sh)
1017

1118
ifeq ($(PROVIDER),aws)

packages/client-proxy/Makefile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ PREFIX := $(strip $(subst ",,$(PREFIX)))
66
HOSTNAME := $(shell hostname 2> /dev/null || hostnamectl hostname 2> /dev/null)
77
$(if $(HOSTNAME),,$(error Failed to determine hostname: both 'hostname' and 'hostnamectl' failed))
88

9+
# Architecture for builds. Defaults to local arch; override for cross-compilation
10+
# (e.g., BUILD_ARCH=amd64 make build-and-upload from an ARM64 host).
11+
BUILD_ARCH ?= $(shell go env GOARCH)
12+
# Docker platform string. Override for multi-arch builds:
13+
# BUILD_PLATFORM=linux/amd64,linux/arm64 make build-and-upload
14+
BUILD_PLATFORM ?= linux/$(BUILD_ARCH)
15+
916
ifeq ($(PROVIDER),aws)
1017
IMAGE_REGISTRY := $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/$(PREFIX)core/client-proxy
1118
else
@@ -16,7 +23,7 @@ endif
1623
build:
1724
# Allow for passing commit sha directly for docker builds
1825
$(eval COMMIT_SHA ?= $(shell git rev-parse --short HEAD))
19-
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/client-proxy -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" .
26+
CGO_ENABLED=0 GOOS=linux GOARCH=$(BUILD_ARCH) go build -o bin/client-proxy -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" .
2027

2128
.PHONY: build-debug
2229
build-debug:
@@ -26,7 +33,7 @@ build-debug:
2633
.PHONY: build-and-upload
2734
build-and-upload:
2835
$(eval COMMIT_SHA := $(shell git rev-parse --short HEAD))
29-
@docker buildx build --platform linux/amd64 --tag $(IMAGE_REGISTRY) --push --build-arg COMMIT_SHA="$(COMMIT_SHA)" -f ./Dockerfile ..
36+
@docker buildx build --platform $(BUILD_PLATFORM) --tag $(IMAGE_REGISTRY) --push --build-arg COMMIT_SHA="$(COMMIT_SHA)" -f ./Dockerfile ..
3037

3138
.PHONY: run
3239
run:

packages/envd/Makefile

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ LDFLAGS=-ldflags "-X=main.commitSHA=$(BUILD)"
77
AWS_BUCKET_PREFIX ?= $(PREFIX)$(AWS_ACCOUNT_ID)-
88
GCP_BUCKET_PREFIX ?= $(GCP_PROJECT_ID)-
99

10+
# Architecture for builds. Defaults to local arch; override for cross-compilation
11+
# (e.g., BUILD_ARCH=amd64 make build from an ARM64 host).
12+
BUILD_ARCH ?= $(shell go env GOARCH)
13+
# Docker platform string. Override for multi-arch builds:
14+
# BUILD_PLATFORM=linux/amd64,linux/arm64 make start-docker
15+
BUILD_PLATFORM ?= linux/$(BUILD_ARCH)
16+
1017
.PHONY: init
1118
init:
1219
brew install protobuf
@@ -20,17 +27,17 @@ else
2027
endif
2128

2229
build:
23-
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin/envd ${LDFLAGS}
30+
CGO_ENABLED=0 GOOS=linux GOARCH=$(BUILD_ARCH) go build -a -o bin/envd ${LDFLAGS}
2431

2532
build-debug:
2633
CGO_ENABLED=1 go build -race -gcflags=all="-N -l" -o bin/debug/envd ${LDFLAGS}
2734

2835
start-docker:
2936
make build
30-
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -t envd-debug . -f debug.Dockerfile
37+
DOCKER_BUILDKIT=1 docker build --platform $(BUILD_PLATFORM) -t envd-debug . -f debug.Dockerfile
3138
docker run \
3239
--name envd \
33-
--platform linux/amd64 \
40+
--platform $(BUILD_PLATFORM) \
3441
-p 49983:49983 \
3542
-p 2345:2345 \
3643
-p 9999:9999 \

packages/orchestrator/Makefile

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ GCP_BUCKET_PREFIX ?= $(GCP_PROJECT_ID)-
77
HOSTNAME := $(shell hostname 2> /dev/null || hostnamectl hostname 2> /dev/null)
88
$(if $(HOSTNAME),,$(error Failed to determine hostname: both 'hostname' and 'hostnamectl' failed))
99

10+
# Architecture for builds. Defaults to local arch; override for cross-compilation
11+
# (e.g., BUILD_ARCH=amd64 make build from an ARM64 host).
12+
BUILD_ARCH ?= $(shell go env GOARCH)
13+
# Docker platform string. Override for multi-arch builds:
14+
# BUILD_PLATFORM=linux/amd64,linux/arm64 make build
15+
BUILD_PLATFORM ?= linux/$(BUILD_ARCH)
16+
1017
.PHONY: init
1118
init:
1219
brew install protobuf
@@ -18,18 +25,18 @@ generate:
1825
.PHONY: build
1926
build:
2027
$(eval COMMIT_SHA := $(shell git rev-parse --short HEAD))
21-
@docker build --platform linux/amd64 --output=bin --build-arg COMMIT_SHA="$(COMMIT_SHA)" -f ./Dockerfile ..
28+
@docker build --platform $(BUILD_PLATFORM) --output=bin --build-arg COMMIT_SHA="$(COMMIT_SHA)" -f ./Dockerfile ..
2229

2330
.PHONY: build-local
24-
build-local:
31+
build-local: fetch-busybox
2532
# Allow for passing commit sha directly for docker builds
2633
$(eval COMMIT_SHA ?= $(shell git rev-parse --short HEAD))
27-
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o bin/orchestrator -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" .
28-
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o bin/clean-nfs-cache -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" ./cmd/clean-nfs-cache
34+
CGO_ENABLED=1 GOOS=linux GOARCH=$(BUILD_ARCH) go build -o bin/orchestrator -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" .
35+
CGO_ENABLED=1 GOOS=linux GOARCH=$(BUILD_ARCH) go build -o bin/clean-nfs-cache -ldflags "-X=main.commitSHA=$(COMMIT_SHA)" ./cmd/clean-nfs-cache
2936

3037
.PHONY: build-debug
31-
build-debug:
32-
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -race -gcflags=all="-N -l" -o bin/orchestrator .
38+
build-debug: fetch-busybox
39+
CGO_ENABLED=1 GOOS=linux GOARCH=$(BUILD_ARCH) go build -race -gcflags=all="-N -l" -o bin/orchestrator .
3340

3441
.PHONY: run-debug
3542
run-debug:
@@ -105,7 +112,7 @@ test-docker:
105112
@cp -r ../shared .shared/
106113
@rm -rf .clickhouse/
107114
@cp -r ../clickhouse .clickhouse/
108-
@docker build --platform linux/amd64 -f test.Dockerfile --no-cache-filter runner --progress=plain -t orchestrator-test .
115+
@docker build --platform $(BUILD_PLATFORM) -f test.Dockerfile --no-cache-filter runner --progress=plain -t orchestrator-test .
109116
@rm -rf .shared/
110117
@rm -rf .clickhouse/
111118
@echo "Done"
@@ -125,6 +132,34 @@ build-template:
125132
-kernel $(KERNEL_VERSION) \
126133
-firecracker $(FIRECRACKER_VERSION)
127134

135+
.PHONY: fetch-busybox
136+
fetch-busybox:
137+
@ARCH=$(BUILD_ARCH); \
138+
BUSYBOX_TARGET=./pkg/template/build/core/systeminit/busybox_1.36.1-2; \
139+
if [ "$$ARCH" != "arm64" ]; then \
140+
echo "✓ Using bundled amd64 busybox"; \
141+
elif file "$$BUSYBOX_TARGET" 2>/dev/null | grep -q 'aarch64\|ARM aarch64'; then \
142+
echo "✓ Busybox is already arm64"; \
143+
elif command -v busybox >/dev/null 2>&1 && file "$$(command -v busybox)" 2>/dev/null | grep -q 'aarch64\|ARM aarch64' && file "$$(command -v busybox)" 2>/dev/null | grep -q 'statically linked'; then \
144+
cp "$$(command -v busybox)" "$$BUSYBOX_TARGET" && \
145+
echo "✓ Copied host busybox (arm64, static) to embedded path"; \
146+
elif command -v apt-get >/dev/null 2>&1 && command -v dpkg-deb >/dev/null 2>&1; then \
147+
echo "Fetching arm64 busybox via apt..."; \
148+
TMPDIR=$$(mktemp -d); \
149+
apt-get download busybox-static 2>/dev/null && \
150+
dpkg-deb -x busybox-static_*.deb "$$TMPDIR" && \
151+
cp "$$TMPDIR/bin/busybox" "$$BUSYBOX_TARGET" && \
152+
rm -rf "$$TMPDIR" busybox-static_*.deb && \
153+
echo "✓ Replaced embedded busybox with arm64 binary (from busybox-static package)" || \
154+
{ rm -rf "$$TMPDIR" busybox-static_*.deb; echo "⚠ apt-get download failed"; exit 1; }; \
155+
else \
156+
echo "⚠ ARM64 busybox required but no method available to fetch it."; \
157+
echo " Options:"; \
158+
echo " 1. Install busybox-static: apt install busybox-static, then re-run"; \
159+
echo " 2. Manually place an arm64 busybox binary at: $$BUSYBOX_TARGET"; \
160+
exit 1; \
161+
fi
162+
128163
.PHONY: migrate
129164
migrate:
130165
./scripts/upload-envs.sh /mnt/disks/fc-envs/v1 $(TEMPLATE_BUCKET_NAME)

packages/shared/pkg/utils/env.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,35 @@ package utils
33
import (
44
"fmt"
55
"os"
6+
"runtime"
67
"strings"
78
)
89

10+
// archAliases normalizes common architecture names to Go convention.
11+
var archAliases = map[string]string{
12+
"amd64": "amd64",
13+
"x86_64": "amd64",
14+
"arm64": "arm64",
15+
"aarch64": "arm64",
16+
}
17+
18+
// TargetArch returns the target architecture for binary paths and OCI platform.
19+
// If TARGET_ARCH is set, it is normalized to Go convention ("amd64" or "arm64");
20+
// otherwise defaults to the host architecture (runtime.GOARCH).
21+
func TargetArch() string {
22+
if arch := os.Getenv("TARGET_ARCH"); arch != "" {
23+
if normalized, ok := archAliases[arch]; ok {
24+
return normalized
25+
}
26+
27+
fmt.Fprintf(os.Stderr, "WARNING: unrecognized TARGET_ARCH=%q, falling back to %s\n", arch, runtime.GOARCH)
28+
29+
return runtime.GOARCH
30+
}
31+
32+
return runtime.GOARCH
33+
}
34+
935
// RequiredEnv returns the value of the environment variable for key if it is set, non-empty and not only whitespace.
1036
// It panics otherwise.
1137
//
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package utils
2+
3+
import (
4+
"runtime"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestTargetArch_DefaultsToHostArch(t *testing.T) {
11+
t.Setenv("TARGET_ARCH", "")
12+
13+
result := TargetArch()
14+
15+
assert.Equal(t, runtime.GOARCH, result)
16+
}
17+
18+
func TestTargetArch_RespectsValidOverride(t *testing.T) {
19+
tests := []struct {
20+
name string
21+
arch string
22+
expected string
23+
}{
24+
{name: "amd64", arch: "amd64", expected: "amd64"},
25+
{name: "arm64", arch: "arm64", expected: "arm64"},
26+
}
27+
28+
for _, tt := range tests {
29+
t.Run(tt.name, func(t *testing.T) {
30+
t.Setenv("TARGET_ARCH", tt.arch)
31+
32+
result := TargetArch()
33+
34+
assert.Equal(t, tt.expected, result)
35+
})
36+
}
37+
}
38+
39+
func TestTargetArch_NormalizesAliases(t *testing.T) {
40+
tests := []struct {
41+
name string
42+
arch string
43+
expected string
44+
}{
45+
{name: "x86_64 → amd64", arch: "x86_64", expected: "amd64"},
46+
{name: "aarch64 → arm64", arch: "aarch64", expected: "arm64"},
47+
}
48+
49+
for _, tt := range tests {
50+
t.Run(tt.name, func(t *testing.T) {
51+
t.Setenv("TARGET_ARCH", tt.arch)
52+
53+
result := TargetArch()
54+
55+
assert.Equal(t, tt.expected, result)
56+
})
57+
}
58+
}
59+
60+
func TestTargetArch_FallsBackOnUnknown(t *testing.T) {
61+
t.Setenv("TARGET_ARCH", "mips")
62+
63+
result := TargetArch()
64+
65+
assert.Equal(t, runtime.GOARCH, result)
66+
}

0 commit comments

Comments
 (0)