mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
316dace11a
Dockerfile (renamed from Dockerfile_init, Dockerfile_update removed):
- Run as a non-root `builder` user matching the host UID/GID (passed as
--build-arg by create() from id -u/id -g), so build output under the
bind mount stays owned by the host user instead of root.
- Fix CCACHE_MAXSIZE: `ccache -M 5G` wrote its limit to a config file
under /ccache at image-build time, but /ccache is a volume mount
point, so that file gets shadowed by the (empty) volume the moment
the container actually runs - the cap never took effect. Set
CCACHE_MAXSIZE=5G as an image ENV instead.
- Dedupe ccache/libffi-devel, add --setopt=install_weak_deps=False
--setopt=tsflags=nodocs, add `git lfs install --system`, combine the
dnf update+install into one layer.
- Drop Dockerfile_update: it built FROM its own previous output, so
every `update` call made the image strictly larger forever (Docker
layers are append-only, `dnf clean` in a later layer can't shrink an
earlier one). `update` now just calls create(), which already runs
`dnf update -y` FROM a clean rockylinux:9 every time.
compose.yaml: pin platform: linux/amd64 so this doesn't silently run
under emulation on an ARM host.
ifcos_env:
- Split the previously-conflated stop/down into six distinct,
Compose-native lifecycle commands: up (create-or-start), down
(remove), stop, start, restart (stop+start, same container),
recreate (down+up, fresh container). Previously `stop` was aliased
to `down`, which silently removed the container instead of pausing
it.
- Implement try(): copies the built wrapper into a real Blender/Bonsai
install for manual testing, reading the target from a new
BLENDER_USER_RESOURCE .env variable and auto-detecting the built
Python version (disambiguating via PY_TGT for multi-version builds).
Deliberately kept human-only - it mutates a live Blender install, so
it shouldn't run unattended as part of an automated/AI workflow,
which should instead copy the wrapper into the repo's own
src/ifcopenshell-python/ifcopenshell/ (documented in SKILL.md).
- Fix unique(): the "has .env already got a UNIQUE_ID line" check
referenced an unset $FILE instead of $ENV_FILE, so it always
evaluated true and appended a fresh "UNIQUE_ID=dummy" line to .env
on every single `up`.
- Minor: differentiate remove()'s log message from down()'s (no longer
identical now that they're distinct operations), tidy help text
alignment and a stray double-space typo in clean().
SKILL.md: rewritten as current-state documentation (no more "fixed in
this copy" changelog framing) covering the above, plus a migration
note for anyone hitting root-owned leftovers from an older image.
Verified by actually building the image and driving every new
lifecycle command (stop/start/restart keep the same container ID;
down+up and recreate produce a new one) and try() (including the
quoted-tilde BLENDER_USER_RESOURCE edge case) against the real container.
Generated with the assistance of an AI coding tool.
(cherry picked from commit 92c50ed3b4)
331 lines
11 KiB
Bash
Executable File
331 lines
11 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 -si "s/^UNIQUE_ID=.*$/UNIQUE_ID=${UNIQUE_ID}/" "$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
|