diff --git a/build_linux.sh b/build_linux.sh index b1e1806c27..938f1005f9 100755 --- a/build_linux.sh +++ b/build_linux.sh @@ -8,7 +8,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")") pushd "${SCRIPT_PATH}" > /dev/null function usage() { - echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]" + echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]" echo " -1: limit builds to one core (where possible)" echo " -j N: limit builds to N cores (where possible)" echo " -b: build in Debug mode" @@ -17,6 +17,8 @@ function usage() { echo " -d: download and build dependencies in ./deps/ (build prerequisite)" echo " -D: dry run" echo " -e: build in RelWithDebInfo mode" + echo " -F: rebuild the cached Docker/Podman runner image from scratch when used with -g" + echo " -g: run the requested build steps inside a Docker/Podman Ubuntu 24.04 container similar to the GitHub Actions Linux runner" echo " -h: prints this help text" echo " -i: build the Orca Slicer AppImage (optional)" echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)" @@ -28,6 +30,9 @@ function usage() { echo " -L: use ld.lld as linker (if available)" echo "For a first use, you want to './${SCRIPT_NAME} -u'" echo " and then './${SCRIPT_NAME} -dsi'" + echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'" + echo "Use './${SCRIPT_NAME} -gF -istrlL' to rebuild the cached runner image first." + echo "Set ORCA_CONTAINER_CLI, ORCA_DOCKER_IMAGE, ORCA_DOCKER_BASE_IMAGE, or ORCA_DOCKER_CMAKE_VERSION to override the container runtime, cached image tag, base image, or CMake version." } SLIC3R_PRECOMPILED_HEADERS="ON" @@ -35,60 +40,83 @@ SLIC3R_PRECOMPILED_HEADERS="ON" unset name BUILD_DIR=build BUILD_CONFIG=Release -while getopts ":1j:bcCdDehiprstulL" opt ; do +FORWARDED_ARGS=() +while getopts ":1j:bcCdDeFghiprstulL" opt ; do case ${opt} in 1 ) export CMAKE_BUILD_PARALLEL_LEVEL=1 + FORWARDED_ARGS+=("-1") ;; j ) export CMAKE_BUILD_PARALLEL_LEVEL=$OPTARG + FORWARDED_ARGS+=("-j" "$OPTARG") ;; b ) BUILD_DIR=build-dbg BUILD_CONFIG=Debug + FORWARDED_ARGS+=("-b") ;; c ) CLEAN_BUILD=1 + FORWARDED_ARGS+=("-c") ;; C ) COLORED_OUTPUT="-DCOLORED_OUTPUT=ON" + FORWARDED_ARGS+=("-C") ;; d ) BUILD_DEPS="1" + FORWARDED_ARGS+=("-d") ;; D ) DRY_RUN="1" + FORWARDED_ARGS+=("-D") ;; e ) BUILD_DIR=build-dbginfo BUILD_CONFIG=RelWithDebInfo + FORWARDED_ARGS+=("-e") + ;; + F ) + CLEAN_DOCKER_IMAGE="1" + ;; + g ) + USE_DOCKER="1" ;; h ) usage exit 1 ;; i ) BUILD_IMAGE="1" + FORWARDED_ARGS+=("-i") ;; p ) SLIC3R_PRECOMPILED_HEADERS="OFF" + FORWARDED_ARGS+=("-p") ;; r ) SKIP_RAM_CHECK="1" + FORWARDED_ARGS+=("-r") ;; s ) BUILD_ORCA="1" + FORWARDED_ARGS+=("-s") ;; t ) BUILD_TESTS="1" + FORWARDED_ARGS+=("-t") ;; u ) export UPDATE_LIB="1" + FORWARDED_ARGS+=("-u") ;; l ) USE_CLANG="1" + FORWARDED_ARGS+=("-l") ;; L ) USE_LLD="1" + FORWARDED_ARGS+=("-L") ;; * ) echo "Unknown argument '${opt}', aborting." @@ -102,6 +130,11 @@ if [ ${OPTIND} -eq 1 ] ; then exit 1 fi +if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] && [[ -z "${USE_DOCKER}" ]] ; then + echo "Error: -F requires -g." + exit 1 +fi + function check_available_memory_and_disk() { FREE_MEM_GB=$(free --gibi --total | grep 'Mem' | rev | cut --delimiter=" " --fields=1 | rev) MIN_MEM_GB=10 @@ -139,6 +172,277 @@ function print_and_run() { fi } +function resolve_container_cli() { + if [[ -n "${ORCA_CONTAINER_CLI}" ]] ; then + if ! command -v "${ORCA_CONTAINER_CLI}" >/dev/null 2>&1 ; then + echo "Error: container runtime '${ORCA_CONTAINER_CLI}' was not found." >&2 + exit 1 + fi + + echo "${ORCA_CONTAINER_CLI}" + return + fi + + if command -v docker >/dev/null 2>&1 ; then + echo "docker" + return + fi + + if command -v podman >/dev/null 2>&1 ; then + echo "podman" + return + fi + + echo "Error: neither docker nor podman is available. Install one of them or set ORCA_CONTAINER_CLI." >&2 + exit 1 +} + +function get_docker_runner_image() { + local base_image + local docker_cmake_version + local recipe_hash + local sanitized_base_image + local sanitized_cmake_version + + if [[ -n "${ORCA_DOCKER_IMAGE}" ]] ; then + echo "${ORCA_DOCKER_IMAGE}" + return + fi + + base_image="${ORCA_DOCKER_BASE_IMAGE:-ubuntu:24.04}" + docker_cmake_version="${ORCA_DOCKER_CMAKE_VERSION-4.3.0}" + recipe_hash=$(find "${SCRIPT_PATH}/build_linux.sh" "${SCRIPT_PATH}/scripts/linux.d" -type f -print0 | sort -z | xargs -0 cat | sha256sum | cut -c1-12) + sanitized_base_image=$(echo "${base_image}" | tr '/:@' '---' | tr -cs 'A-Za-z0-9_.-' '-') + sanitized_cmake_version=$(echo "${docker_cmake_version:-system}" | tr -cs 'A-Za-z0-9_.-' '-') + echo "orcaslicer-linux-builder:${sanitized_base_image}-cmake-${sanitized_cmake_version}-${recipe_hash}" +} + +function docker_runner_dockerfile() { + cat <<'EOF' +ARG BASE_IMAGE=ubuntu:24.04 +FROM ${BASE_IMAGE} + +ARG CMAKE_VERSION=4.3.0 + +ENV DEBIAN_FRONTEND=noninteractive +SHELL ["/bin/bash", "-c"] + +RUN apt-get update && apt-get install -y sudo ca-certificates curl tar + +COPY build_linux.sh /tmp/orcaslicer/build_linux.sh +COPY scripts/linux.d /tmp/orcaslicer/scripts/linux.d + +WORKDIR /tmp/orcaslicer + +RUN chmod +x ./build_linux.sh +RUN ./build_linux.sh -ur +RUN if [[ -n "${CMAKE_VERSION}" ]] ; then \ + case "$(uname -m)" in \ + x86_64|amd64) cmake_arch="x86_64" ;; \ + aarch64|arm64) cmake_arch="aarch64" ;; \ + *) cmake_arch="" ;; \ + esac ; \ + if [[ -n "${cmake_arch}" ]] ; then \ + cmake_root="/opt/cmake-${CMAKE_VERSION}-linux-${cmake_arch}" ; \ + if ! curl -fsSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.tar.gz" | tar -xz -C /opt ; then \ + echo "Warning: failed to install CMake ${CMAKE_VERSION}; falling back to the distro cmake package." ; \ + elif [[ -d "${cmake_root}" ]] ; then \ + ln -sf "${cmake_root}/bin/"* /usr/local/bin/ ; \ + fi ; \ + else \ + echo "Skipping GitHub Actions CMake install for unsupported architecture $(uname -m)." ; \ + fi ; \ + fi +RUN rm -rf /var/lib/apt/lists/* /tmp/orcaslicer +EOF +} + +function ensure_docker_runner_image() { + local container_cli + local base_image + local runner_image + local docker_cmake_version + local image_exists="0" + local force_rebuild="0" + local -a build_cmd + + container_cli="$1" + runner_image="$2" + base_image="${ORCA_DOCKER_BASE_IMAGE:-ubuntu:24.04}" + docker_cmake_version="${ORCA_DOCKER_CMAKE_VERSION-4.3.0}" + + if "${container_cli}" image inspect "${runner_image}" >/dev/null 2>&1 ; then + image_exists="1" + fi + + if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] ; then + force_rebuild="1" + if [[ "${image_exists}" == "1" ]] ; then + echo "Removing cached container image ${runner_image} ..." + if [[ -z "${DRY_RUN}" ]] ; then + "${container_cli}" image rm -f "${runner_image}" >/dev/null + else + printf '%q ' "${container_cli}" image rm -f "${runner_image}" + echo + fi + image_exists="0" + fi + fi + + if [[ "${image_exists}" == "1" ]] ; then + echo "Using cached container image ${runner_image}" + return + fi + + build_cmd=( + "${container_cli}" build --pull + -t "${runner_image}" + --build-arg "BASE_IMAGE=${base_image}" + --build-arg "CMAKE_VERSION=${docker_cmake_version}" + ) + if [[ "${force_rebuild}" == "1" ]] ; then + build_cmd+=(--no-cache) + fi + build_cmd+=(-f - "${SCRIPT_PATH}") + + printf '%q ' "${build_cmd[@]}" + echo + if [[ -n "${DRY_RUN}" ]] ; then + return + fi + + docker_runner_dockerfile | "${build_cmd[@]}" +} + +function run_in_docker() { + local container_cli + local runner_image + local container_workspace + local host_uid + local host_gid + local host_user + local -a build_args + local -a container_env + + container_cli=$(resolve_container_cli) + runner_image=$(get_docker_runner_image) + host_uid=$(id -u) + host_gid=$(id -g) + host_user="${USER:-orca}" + container_workspace="/__w/OrcaSlicer/OrcaSlicer" + build_args=() + for item in "${FORWARDED_ARGS[@]}" ; do + if [[ "${item}" == "-u" ]] || [[ "${item}" == "-D" ]] ; then + continue + fi + + build_args+=("${item}") + done + + container_env=( + -e "CI=true" + -e "GITHUB_ACTIONS=true" + -e "GITHUB_WORKSPACE=${container_workspace}" + -e "GIT_CEILING_DIRECTORIES=${container_workspace}/deps/build" + -e "RUNNER_OS=Linux" + -e "RUNNER_TEMP=/tmp" + -e "HOST_UID=${host_uid}" + -e "HOST_GID=${host_gid}" + -e "HOST_USER=${host_user}" + ) + if [[ -n "${CMAKE_BUILD_PARALLEL_LEVEL}" ]] ; then + container_env+=( -e "CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL}" ) + fi + if [[ -n "${ORCA_UPDATER_SIG_KEY}" ]] ; then + container_env+=( -e "ORCA_UPDATER_SIG_KEY=${ORCA_UPDATER_SIG_KEY}" ) + fi + + ensure_docker_runner_image "${container_cli}" "${runner_image}" + + printf '%q ' "${container_cli}" run --rm -i \ + -v "${SCRIPT_PATH}:${container_workspace}" \ + -w "${container_workspace}" \ + "${container_env[@]}" \ + "${runner_image}" \ + bash -s -- "${build_args[@]}" + echo + if [[ -n "${DRY_RUN}" ]] ; then + return + fi + + "${container_cli}" run --rm -i \ + -v "${SCRIPT_PATH}:${container_workspace}" \ + -w "${container_workspace}" \ + "${container_env[@]}" \ + "${runner_image}" \ + bash -s -- "${build_args[@]}" <<'EOF' +set -e + +function create_builder_user() { + if [[ "${HOST_UID}" == "0" ]] ; then + HOST_USER=root + return + fi + + if getent group "${HOST_GID}" >/dev/null 2>&1 ; then + HOST_GROUP=$(getent group "${HOST_GID}" | cut -d: -f1) + else + HOST_GROUP="orca-builder" + if getent group "${HOST_GROUP}" >/dev/null 2>&1 ; then + HOST_GROUP="orca-builder-${HOST_GID}" + fi + groupadd -g "${HOST_GID}" "${HOST_GROUP}" + fi + + if getent passwd "${HOST_UID}" >/dev/null 2>&1 ; then + HOST_USER=$(getent passwd "${HOST_UID}" | cut -d: -f1) + usermod -g "${HOST_GROUP}" "${HOST_USER}" + elif id -u "${HOST_USER}" >/dev/null 2>&1 ; then + usermod -u "${HOST_UID}" -g "${HOST_GROUP}" "${HOST_USER}" + else + useradd -m -u "${HOST_UID}" -g "${HOST_GROUP}" -s /bin/bash "${HOST_USER}" + fi + + echo "${HOST_USER} ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/orcaslicer-builder + chmod 0440 /etc/sudoers.d/orcaslicer-builder +} + +create_builder_user +mkdir -p "${GITHUB_WORKSPACE}/deps/build/destdir" +chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/deps/build" +if [[ -d "${GITHUB_WORKSPACE}/build" ]] ; then + chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build" +fi +if [[ -d "${GITHUB_WORKSPACE}/build-dbg" ]] ; then + chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build-dbg" +fi +if [[ -d "${GITHUB_WORKSPACE}/build-dbginfo" ]] ; then + chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build-dbginfo" +fi + +sudo -H -u "${HOST_USER}" env \ + CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL-}" \ + GITHUB_WORKSPACE="${GITHUB_WORKSPACE}" \ + GIT_CEILING_DIRECTORIES="${GIT_CEILING_DIRECTORIES-}" \ + ORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY-}" \ + bash -c ' + set -e + cd "${GITHUB_WORKSPACE}" + if [[ "$#" -gt 0 ]] ; then + ./build_linux.sh "$@" + else + echo "No build steps were requested after container setup." + fi + ' bash "$@" +EOF +} + +if [[ -n "${USE_DOCKER}" ]] ; then + run_in_docker + popd > /dev/null # ${SCRIPT_PATH} + exit 0 +fi + # cmake 4.x compatibility workaround export CMAKE_POLICY_VERSION_MINIMUM=3.5