From f25b072fa0f5ebd29ecf67f070d75484e0c7aa57 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Mon, 13 Jul 2026 06:44:55 +0300 Subject: [PATCH] docker: make the build env work on macOS / Apple Silicon hosts Three host-portability fixes to the docker/ toolchain from #8564 so it runs on macOS as well as Linux. All three are no-ops on native amd64 Linux. 1. Dockerfile: only groupadd when the target GID is free. macOS's default primary group `staff` is GID 20, which already exists as `games` in rockylinux:9, so `groupadd -g 20` aborted the image build. Guard with `getent group "${USER_GID}" || groupadd ...`; useradd -g accepts the existing GID. 2. ifcos_env unique(): replace GNU-only `sed -si` (BSD/macOS sed errors "illegal option -- s") with a portable `sed > tmp && mv` rewrite of the UNIQUE_ID line. Verified against macOS BSD sed. 3. create() + compose.yaml: build with an explicit `--platform linux/amd64` so the locally built image's platform matches the `platform: linux/amd64` pin in compose.yaml. Without it, on arm64 the local image is tagged linux/arm64, compose treats the platform-mismatched image as absent and tries to pull `ifcopenshell-build-env:updated` from Docker Hub (which does not exist -> access denied). Also add `pull_policy: never` as a safety net so a future mismatch surfaces as a clear "image not found" rather than a registry auth error. Note: on Apple Silicon the amd64 build runs under emulation and a cold full build is slow; ccache makes incremental rebuilds tolerable. A native Linux/Intel host or CI remains the better choice for routine use, but these fixes turn "hard broken" into "works with a caveat" on macOS. This change was made with the assistance of an AI tool. Co-Authored-By: Claude Fable 5 --- docker/Dockerfile | 10 +++++++++- docker/compose.yaml | 8 ++++++++ docker/ifcos_env | 22 +++++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index fbc4c23baf..4133cf96f1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -39,7 +39,15 @@ ENV PATH="/usr/lib/ccache:$PATH" # if your host user has a different UID/GID. ARG USER_UID=1000 ARG USER_GID=1000 -RUN groupadd -g "${USER_GID}" builder \ +# groupadd fails outright if USER_GID is already taken by an existing +# system group - which happens whenever a host's primary GID collides with +# one baked into the rockylinux9 base image. The main real-world case is +# macOS, where the default user's primary group is "staff" at GID 20, and +# GID 20 is "games" on RHEL-family images. Only create the "builder" group +# when that GID is actually free; otherwise useradd just attaches to +# whichever group already owns it. Either way the builder user ends up +# with the right GID for bind-mount ownership, which is all that matters. +RUN (getent group "${USER_GID}" >/dev/null || groupadd -g "${USER_GID}" builder) \ && useradd -m -u "${USER_UID}" -g "${USER_GID}" -s /bin/bash builder \ && echo "builder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/builder diff --git a/docker/compose.yaml b/docker/compose.yaml index 9bc79234d1..fcd5f17745 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -4,6 +4,14 @@ services: container_name: ifcopenshell-${UNIQUE_ID} image: ifcopenshell-build-env:updated platform: linux/amd64 + # There's no `build:` section - the image is always produced ahead of + # time by `./ifcos_env create` (`docker build`, not `docker compose + # build`). Without this, a platform mismatch between the pin above and + # whatever's in the local image store makes compose treat the image as + # absent and fall back to pulling ifcopenshell-build-env:updated from + # Docker Hub, where it doesn't exist. Fail fast with a clear "not + # found" instead of an obscure registry access-denied error. + pull_policy: never volumes: - type: bind source: ../ diff --git a/docker/ifcos_env b/docker/ifcos_env index 4286421572..c35ba85d2e 100755 --- a/docker/ifcos_env +++ b/docker/ifcos_env @@ -24,7 +24,18 @@ set_env function create() { echo "⭐ Creating image: ifcopenshell-build-env" + # compose.yaml pins the service to platform: linux/amd64 (this stack + # always targets the rockylinux9-x64 build-outputs branch and produces + # linux64 artifacts, regardless of host arch). Building without + # --platform would tag the image for the host's native arch instead - + # harmless on an amd64 host, but on an arm64 host (e.g. Apple Silicon) + # it leaves a local image that doesn't match what compose asked for, so + # `docker compose up` decides the requested platform is "missing" and + # tries to pull ifcopenshell-build-env:updated from Docker Hub instead + # of using the image just built. Pinning the build platform here keeps + # the local image's arch in sync with compose's pin on every host. docker build -f Dockerfile \ + --platform linux/amd64 \ --build-arg USER_UID="$(id -u)" --build-arg USER_GID="$(id -g)" \ -t ifcopenshell-build-env:updated . } @@ -112,7 +123,16 @@ function unique() { echo -e "\nUNIQUE_ID=dummy\n" >> "$ENV_FILE" fi - export UNIQUE_ID="$(pwd | sha256sum | cut -c -8)" && sed -si "s/^UNIQUE_ID=.*$/UNIQUE_ID=${UNIQUE_ID}/" "$ENV_FILE" + export UNIQUE_ID="$(pwd | sha256sum | cut -c -8)" + + # `sed -i` takes incompatible syntax between GNU sed (Linux) and BSD sed + # (macOS) - `-si` is GNU-only and errors as "illegal option -- s" under + # BSD/macOS sed. Avoid -i altogether and do the in-place edit via a temp + # file + mv instead, which behaves identically with either sed. + local tmp_file + tmp_file="$(mktemp "${ENV_FILE}.XXXXXX")" + sed "s/^UNIQUE_ID=.*$/UNIQUE_ID=${UNIQUE_ID}/" "$ENV_FILE" > "$tmp_file" + mv "$tmp_file" "$ENV_FILE" set_env }