mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
c3469a5da2
Two host-environment bugs in the build-env scripts that break on
macOS/Apple Silicon hosts, independent of target architecture:
- Dockerfile: groupadd fails outright when USER_GID collides with an
existing system group in the rockylinux9 base image (e.g. macOS
default user GID 20 "staff" collides with RHEL's GID 20 "games").
Guard with getent so useradd attaches to the existing group instead.
- ifcos_env: `sed -si` is GNU-only syntax and errors under BSD/macOS
sed. Do the UNIQUE_ID substitution via a portable temp-file + mv.
Per sboddy's review on the original PR: dropped the linux/amd64
platform-pin additions from this change. The stack already targets
Rocky9/x64 build outputs by design, and Docker Desktop on macOS has
no native container runtime regardless (it's a Linux VM either way),
so forcing the image to run under emulation doesn't produce anything
that's actually loadable into a native macOS Blender/Bonsai install.
That's a separate, harder problem worth solving via a native build
path instead (mirroring build_osx.yml), not by fighting emulation
here. These two fixes stand on their own merits on any host.
This change was made with the assistance of an AI tool.
(cherry picked from commit 8b05510d6c)
340 lines
12 KiB
Bash
Executable File
340 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ================== CONFIG ==================
|
|
SCRIPT_NAME=$(basename "$0")
|
|
ENV_FILE=".env"
|
|
WORKDIR="/__w/IfcOpenShell/IfcOpenShell"
|
|
NAMEPREFIX=ifcopenshell
|
|
|
|
function set_env() {
|
|
# Load .env file if it exists
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
set -a
|
|
source "$ENV_FILE"
|
|
set +a
|
|
echo "✅ Loaded environment variables from $ENV_FILE"
|
|
else
|
|
echo "⚠️ No $ENV_FILE found, proceeding without it."
|
|
fi
|
|
}
|
|
|
|
set_env
|
|
|
|
# ================ FUNCTIONS =================
|
|
|
|
function create() {
|
|
echo "⭐ Creating image: ifcopenshell-build-env"
|
|
docker build -f Dockerfile \
|
|
--build-arg USER_UID="$(id -u)" --build-arg USER_GID="$(id -g)" \
|
|
-t ifcopenshell-build-env:updated .
|
|
}
|
|
|
|
function update() {
|
|
# The Dockerfile always builds FROM a clean rockylinux:9 and does
|
|
# `dnf update -y` as its first step, so re-running create() is enough
|
|
# to get fresh packages.
|
|
echo "⚡ Updating image: ifcopenshell-build-env"
|
|
create
|
|
}
|
|
|
|
function up() {
|
|
# Creates the container if it doesn't exist yet (and starts it either
|
|
# way) - this is the one that needs ready_repo, since a freshly created
|
|
# container has no submodules/dependency cache in place yet.
|
|
echo "🚀 Creating/starting stack: ifcopenshell-${UNIQUE_ID}"
|
|
unique # Update UNIQUE_ID first
|
|
docker compose up -d "$@" # Container must exist before ready_repo can exec into it.
|
|
ready_repo # Ensure repo is recursive, and the build repo is in place.
|
|
}
|
|
|
|
function down() {
|
|
# Removes the container (and its network) entirely. Named volumes
|
|
# (ccache) and the bind-mounted repo/build/ survive; up() will recreate
|
|
# the container from scratch next time.
|
|
echo "🔥 Removing stack: ifcopenshell-${UNIQUE_ID}"
|
|
docker compose down "$@"
|
|
}
|
|
|
|
function stop() {
|
|
# Stops the existing container without removing it - the container,
|
|
# its filesystem layer, and its exec history all remain intact.
|
|
echo "🛑 Stopping stack: ifcopenshell-${UNIQUE_ID}"
|
|
docker compose stop "$@"
|
|
}
|
|
|
|
function start() {
|
|
# Starts a previously-stopped container back up. Does nothing (and
|
|
# won't create anything) if the container doesn't exist - use up() for
|
|
# that.
|
|
echo "▶️ Starting stack: ifcopenshell-${UNIQUE_ID}"
|
|
docker compose start "$@"
|
|
}
|
|
|
|
function restart() {
|
|
echo "🔄 Restarting stack (stop, then start)..."
|
|
stop
|
|
start
|
|
}
|
|
|
|
function recreate() {
|
|
echo "♻️ Recreating stack (down, then up)..."
|
|
down
|
|
up
|
|
}
|
|
|
|
function logs() {
|
|
echo "📜 Showing logs..."
|
|
docker compose logs -f "$@"
|
|
}
|
|
|
|
function ps() {
|
|
docker compose ps
|
|
}
|
|
|
|
function config() {
|
|
echo "🔍 Validated compose configuration:"
|
|
docker compose config
|
|
}
|
|
|
|
function remove() {
|
|
# Lower-level than down(): removes already-stopped containers without
|
|
# touching the compose network. Mostly useful after a plain stop().
|
|
echo "🗑️ Removing stopped containers: ifcopenshell-${UNIQUE_ID}"
|
|
docker compose rm "$@"
|
|
}
|
|
|
|
function unique() {
|
|
echo "🔧 Making stack name folder specific..."
|
|
|
|
REGEX="^UNIQUE_ID="
|
|
|
|
if [[ ! -f "$ENV_FILE" ]] || ! grep -qE "$REGEX" "$ENV_FILE"; then
|
|
echo -e "\nUNIQUE_ID=dummy\n" >> "$ENV_FILE"
|
|
fi
|
|
|
|
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
|
|
}
|
|
|
|
function ready_repo() {
|
|
echo "👍 Getting the repo ready to build..."
|
|
docker exec -i -w "${WORKDIR}" "${NAMEPREFIX}-${UNIQUE_ID}" bash -c '
|
|
set -euo pipefail # Recommended for robustness
|
|
|
|
git submodule update --init --recursive
|
|
|
|
if [[ ! -d "build" ]]; then
|
|
git clone -b rockylinux9-x64 https://github.com/IfcOpenShell/build-outputs.git build
|
|
else
|
|
cd build
|
|
git pull
|
|
cd ..
|
|
fi
|
|
|
|
if [[ ! -d "build/Linux/x86_64/install/boost-1.86.0/" ]]; then
|
|
cd build
|
|
uv run ../nix/cache_dependencies.py unpack
|
|
cd ..
|
|
fi
|
|
'
|
|
}
|
|
|
|
function build() {
|
|
echo "☕ Execute the build, go make yourself a cuppa... I'll be a while"
|
|
local BUILD_TARGET="$1"
|
|
|
|
docker exec -i -w "${WORKDIR}" -e PY_TGT="${PY_TGT}" -e BUILD_TARGET="${BUILD_TARGET}" "${NAMEPREFIX}-${UNIQUE_ID}" bash -c '
|
|
set -o pipefail
|
|
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release uv run ./nix/build-all.py -v ${PY_TGT:+-$PY_TGT} --diskcleanup ${BUILD_TARGET} 2>&1 | tee build.log
|
|
'
|
|
echo "🎒 Pack Dependencies"
|
|
docker exec -i -w "${WORKDIR}" "${NAMEPREFIX}-${UNIQUE_ID}" bash -c '
|
|
cd build
|
|
uv run ../nix/cache_dependencies.py pack
|
|
'
|
|
|
|
echo "🎁 Package .zip archives"
|
|
docker exec -i -w "${WORKDIR}" -e GITHUB_SHA="$(git rev-parse HEAD)" "${NAMEPREFIX}-${UNIQUE_ID}" bash -c '
|
|
OUTPUT_DIR=${PWD}/output
|
|
VERSION=v`cat VERSION`
|
|
mkdir -p ${OUTPUT_DIR}
|
|
cd ./build/`uname`/*/install/ifcopenshell
|
|
|
|
ls -d python-* | while read py_version; do
|
|
postfix=`echo ${py_version: -1} | sed s/[0-9]//`
|
|
numbers=`echo $py_version | grep -oE "[0-9]+\.[0-9]+" | tr -d "."`
|
|
py_version_major=python-${numbers}$postfix
|
|
pushd . > /dev/null
|
|
cd $py_version
|
|
if [ ! -d ifcopenshell ]; then
|
|
mkdir ../ifcopenshell_
|
|
mv * ../ifcopenshell_
|
|
mv ../ifcopenshell_ ifcopenshell
|
|
fi
|
|
[ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__
|
|
find ifcopenshell -name "*.pyc" -delete
|
|
zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip ifcopenshell/*
|
|
mv *.zip ${OUTPUT_DIR}/
|
|
popd > /dev/null
|
|
done
|
|
|
|
cd bin
|
|
if compgen -G "./*.zip" > /dev/null; then
|
|
rm *.zip 2>&1 >/dev/null || true
|
|
ls | while read exe; do
|
|
zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-linux64.zip $exe
|
|
done
|
|
mv *.zip ${OUTPUT_DIR}/
|
|
cd ..
|
|
'
|
|
}
|
|
|
|
function attach() {
|
|
echo "🔦 Connect to interactive shell"
|
|
docker exec -it -w "${WORKDIR}" "${NAMEPREFIX}-${UNIQUE_ID}" /bin/bash
|
|
}
|
|
|
|
function try() {
|
|
# Copies the freshly built wrapper into your actual Blender/Bonsai
|
|
# installation for manual, in-Blender testing. This is a human-only
|
|
# convenience: it overwrites files in your live Blender setup, so it's
|
|
# not something that should run unattended as part of an automated or
|
|
# AI-driven build/test loop (which should instead copy the wrapper into
|
|
# the repo's own src/ifcopenshell-python/ifcopenshell/ - see SKILL.md).
|
|
echo "🚴 Copying build artifacts into your Blender resource folder for testing"
|
|
|
|
if [[ -z "${BLENDER_USER_RESOURCE:-}" ]]; then
|
|
echo "❌ BLENDER_USER_RESOURCE is not set in .env."
|
|
echo " Add a line pointing at wherever Blender's user resource folder for"
|
|
echo " the Bonsai extension actually is on your system, e.g.:"
|
|
echo " BLENDER_USER_RESOURCE=~/.config/blender/bonsai/"
|
|
return 1
|
|
fi
|
|
|
|
# Normalise: expand a leading ~ (in case it was quoted in .env and so
|
|
# never went through shell tilde-expansion when set_env sourced it),
|
|
# then resolve to an absolute, symlink-free path.
|
|
local resource="${BLENDER_USER_RESOURCE/#\~/$HOME}"
|
|
resource="$(realpath -m "$resource")"
|
|
|
|
local install_dir="../build/Linux/x86_64/install"
|
|
local py_dirs=("$install_dir"/python-*)
|
|
if [[ ${#py_dirs[@]} -gt 1 && -n "${PY_TGT:-}" ]]; then
|
|
# PY_TGT is compact (py-311); the install dirs are dotted
|
|
# (python-3.11.8) - reinsert the dot (assumes a single-digit major
|
|
# version, true for the Python 3.x line) before matching.
|
|
local py_tgt_digits="${PY_TGT#py-}"
|
|
local py_tgt_dotted="${py_tgt_digits:0:1}.${py_tgt_digits:1}"
|
|
local filtered=() d
|
|
for d in "${py_dirs[@]}"; do
|
|
[[ "$(basename "$d")" == "python-${py_tgt_dotted}."* ]] && filtered+=("$d")
|
|
done
|
|
[[ ${#filtered[@]} -gt 0 ]] && py_dirs=("${filtered[@]}")
|
|
fi
|
|
if [[ ${#py_dirs[@]} -ne 1 || ! -d "${py_dirs[0]}" ]]; then
|
|
echo "❌ Expected exactly one built python-* dir under $install_dir, found ${#py_dirs[@]}."
|
|
echo " Run 'build' first, or set PY_TGT in .env to disambiguate a multi-version build."
|
|
return 1
|
|
fi
|
|
|
|
local py_minor
|
|
py_minor="$(basename "${py_dirs[0]}" | grep -oE '[0-9]+\.[0-9]+')"
|
|
local wrapper_dir="${py_dirs[0]}/lib/python${py_minor}/site-packages/ifcopenshell"
|
|
if [[ ! -f "$wrapper_dir/ifcopenshell_wrapper.py" ]]; then
|
|
echo "❌ Built wrapper not found at $wrapper_dir - run 'build' first."
|
|
return 1
|
|
fi
|
|
|
|
local target="$resource/extensions/.local/lib/python${py_minor}/site-packages/ifcopenshell"
|
|
mkdir -p "$target"
|
|
cp "$wrapper_dir"/_ifcopenshell_wrapper*.so "$target/"
|
|
cp "$wrapper_dir"/ifcopenshell_wrapper.py "$target/"
|
|
echo "✅ Copied wrapper into $target"
|
|
}
|
|
|
|
function clean() {
|
|
# Host-side only - doesn't touch the container, image, or ccache volume.
|
|
echo "💎 Clean the build and output folder up"
|
|
if [[ -d "../build" ]]; then
|
|
rm -rf ../build
|
|
fi
|
|
if [[ -d "../output" ]]; then
|
|
rm -rf ../output
|
|
fi
|
|
}
|
|
|
|
|
|
function help() {
|
|
cat <<EOF
|
|
Usage: ./$SCRIPT_NAME <command>
|
|
|
|
Available commands:
|
|
create Build the rocky9-based image
|
|
update Rebuild the image fresh, picking up OS package updates
|
|
up Create the container if it doesn't exist yet, and start it
|
|
down Remove the container entirely (docker compose down)
|
|
stop Stop the container without removing it
|
|
start Start a previously-stopped container
|
|
restart stop, then start (same container, no recreation)
|
|
recreate down, then up (fresh container)
|
|
build Execute the IfcOpenShell build
|
|
attach Connect to an interactive shell in the container
|
|
try Copy the built wrapper into your Blender resource folder
|
|
(human-only - see BLENDER_USER_RESOURCE below, and SKILL.md
|
|
for the AI/automated-testing equivalent)
|
|
clean Remove the build and output folders
|
|
logs Follow container logs
|
|
ps Show running containers
|
|
config Validate and show compose config
|
|
remove Remove stopped containers (docker compose rm)
|
|
help Show this help
|
|
|
|
Environment variables from .env are automatically loaded, including:
|
|
PY_TGT Restrict the build to one Python version, e.g. py-311
|
|
UNIQUE_ID Recalculated automatically on every 'up', don't set by hand
|
|
BLENDER_USER_RESOURCE Where 'try' copies the wrapper for manual testing, e.g.
|
|
~/.config/blender/bonsai/
|
|
EOF
|
|
}
|
|
|
|
# ================= MAIN =================
|
|
|
|
case "$1" in
|
|
create) create ;;
|
|
update) update ;;
|
|
up) up "${@:2}" ;;
|
|
down) down "${@:2}" ;;
|
|
stop) stop "${@:2}" ;;
|
|
start) start "${@:2}" ;;
|
|
restart) restart ;;
|
|
recreate) recreate ;;
|
|
build) build "${@:2}" ;;
|
|
attach) attach ;;
|
|
try) try ;;
|
|
clean) clean ;;
|
|
logs) logs "${@:2}" ;;
|
|
ps) ps ;;
|
|
config) config ;;
|
|
remove) remove ;;
|
|
help|-h|--help) help ;;
|
|
"")
|
|
echo "❌ No command provided."
|
|
help
|
|
;;
|
|
*)
|
|
echo "❌ Unknown command: $1"
|
|
echo "Type './$SCRIPT_NAME help' for available commands."
|
|
exit 1
|
|
;;
|
|
esac
|