Files
clice/docker/linux/Dockerfile
sora-mono f94927dc1d Dev Container Support (#248)
Co-authored-by: star9029 <hengxings783@gmail.com>
2025-09-19 23:28:36 +08:00

266 lines
8.6 KiB
Docker

# build with multi-stage for cache efficiency
FROM ubuntu:24.04 AS basic-tools
# allow build script to bind-mount project source into build container (host path)
ARG BUILD_SRC
# set non-interactive frontend to avoid prompts
ENV DEBIAN_FRONTEND=noninteractive
# ensure user-local bin is on PATH for non-apt installs (xmake, uv, python)
ENV PATH="/root/.local/bin:${PATH}"
# install basic tools
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
# TODO: support more cache for python, xmake installation
# TODO: check why cache doesn't work after add-apt-repository, may we change it to cache?
bash -eux - <<'BASH'
set -e
apt update
# first install minimal apt prerequisites
# software-properties-common for add-apt-repository
# gnupg for gpg to verify cmake installer
# curl, git for downloading sources
# xz-utils, unzip for extracting archives
# make for xmake installation
apt install -y --no-install-recommends \
software-properties-common \
curl \
gnupg \
git \
xz-utils \
unzip \
make
# gcc, llvm PPA
add-apt-repository -y ppa:ubuntu-toolchain-r/ppa
apt update
BASH
# Compiler stage
FROM basic-tools AS compiler-stage
# passed from build arg
ARG COMPILER
# copy instead of bind-mount, to avoid docker build cache invalidation
COPY config/default-toolchain-version /clice/config/default-toolchain-version
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
bash -eux - <<'BASH'
set -e
# Always install libstdc++ development files, required for both gcc and clang to link against libstdc++
GCC_VERSION=$(grep -E '^gcc,' /clice/config/default-toolchain-version | cut -d',' -f2)
apt install -y --no-install-recommends "libstdc++-${GCC_VERSION}-dev"
if [ "$COMPILER" = "gcc" ]; then
apt install -y --no-install-recommends "gcc-${GCC_VERSION}" "g++-${GCC_VERSION}"
update-alternatives --install /usr/bin/cc cc "/usr/bin/gcc-${GCC_VERSION}" 100
update-alternatives --install /usr/bin/gcc gcc "/usr/bin/gcc-${GCC_VERSION}" 100
update-alternatives --install /usr/bin/c++ c++ "/usr/bin/g++-${GCC_VERSION}" 100
update-alternatives --install /usr/bin/g++ g++ "/usr/bin/g++-${GCC_VERSION}" 100
elif [ "$COMPILER" = "clang" ]; then
CLANG_VERSION=$(grep -E '^clang,' /clice/config/default-toolchain-version | cut -d',' -f2)
# install clang toolchain, libstdc++ is already installed
apt install -y --no-install-recommends "clang-${CLANG_VERSION}" "clang-tools-${CLANG_VERSION}" "lld-${CLANG_VERSION}" "libclang-rt-${CLANG_VERSION}-dev"
update-alternatives --install /usr/bin/clang clang "/usr/bin/clang-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/clang++ clang++ "/usr/bin/clang++-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/c++ c++ "/usr/bin/clang++-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/cc cc "/usr/bin/clang-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/ld ld "/usr/bin/lld-${CLANG_VERSION}" 100
else
echo "Error: Unsupported compiler '$COMPILER'. Use 'gcc' or 'clang'." >&2; exit 1
fi
BASH
FROM compiler-stage AS build-tool-stage
ARG XMAKE_CACHE_DIR="/docker-build-cache/xmake"
ARG CMAKE_CACHE_DIR="/docker-build-cache/cmake"
ARG UV_CACHE_DIR="/var/cache/uv"
ENV XMAKE_CACHE_DIR=${XMAKE_CACHE_DIR}
ENV CMAKE_CACHE_DIR=${CMAKE_CACHE_DIR}
ENV UV_CACHE_DIR=${UV_CACHE_DIR}
COPY ./pyproject.toml /clice/pyproject.toml
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=${XMAKE_CACHE_DIR},sharing=locked \
--mount=type=cache,target=${CMAKE_CACHE_DIR},sharing=locked \
--mount=type=cache,target=${UV_CACHE_DIR},sharing=locked \
bash -eux - <<'BASH'
install_xmake() {
set -e
XMAKE_VERSION=$(grep -E '^xmake,' /clice/config/default-toolchain-version | cut -d',' -f2)
XMAKE_BASE_URL="https://github.com/xmake-io/xmake/releases/download/v${XMAKE_VERSION}"
XMAKE_FILENAME="xmake-bundle-v${XMAKE_VERSION}.linux.x86_64"
XMAKE_CACHED_FILE="${XMAKE_CACHE_DIR}/${XMAKE_FILENAME}"
if [ ! -f "${XMAKE_CACHED_FILE}" ] ; then
rm -f "${XMAKE_CACHE_DIR}/*"
curl -fsSL --retry 3 -o "${XMAKE_CACHED_FILE}" "${XMAKE_BASE_URL}/${XMAKE_FILENAME}"
fi
XMAKE_INSTALL_DIR="/usr/bin"
XMAKE_INSTALLED_FILE="${XMAKE_INSTALL_DIR}/${XMAKE_FILENAME}"
cp "${XMAKE_CACHED_FILE}" "${XMAKE_INSTALLED_FILE}"
chmod +x "${XMAKE_INSTALLED_FILE}"
update-alternatives --install /usr/bin/xmake xmake "${XMAKE_INSTALLED_FILE}" 100
echo "export XMAKE_ROOT=y" >> ~/.bashrc
}
# Attention: DO NOT install cmake via PPA with apt, which would have to install required build-essential compiler tool chain
# We SHOULD NOT install another compiler toolchain, which could cause a lot trouble
# And we should not install compiler toolchain away from compiler stage
# So we install cmake from official installer script, and cache the downloaded files
install_cmake() {
set -e
# cached downloads live under /docker-build-cache/cmake (BuildKit cache mount)
CMAKE_VERSION=$(grep -E '^cmake,' /clice/config/default-toolchain-version | cut -d',' -f2)
ARCH="x86_64"
BASE_URL="https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}"
INSTALLER_FILENAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh"
SHA_FILENAME="cmake-${CMAKE_VERSION}-SHA-256.txt"
ASC_FILENAME="${SHA_FILENAME}.asc"
INSTALLER_PATH="${CMAKE_CACHE_DIR}/${INSTALLER_FILENAME}"
SHA_PATH="${CMAKE_CACHE_DIR}/${SHA_FILENAME}"
ASC_PATH="${CMAKE_CACHE_DIR}/${ASC_FILENAME}"
verify_cmake_installer() {
if ! gpg --verify "${ASC_PATH}" "${SHA_PATH}"; then
echo "Signature verification failed for ${SHA_FILENAME}." >&2
return 1
fi
local expected_hash
expected_hash=$(grep "${INSTALLER_FILENAME}" "${SHA_PATH}" | awk '{print $1}')
local actual_hash
actual_hash=$(sha256sum "${INSTALLER_PATH}" | awk '{print $1}')
if [ "${expected_hash}" != "${actual_hash}" ]; then
echo "Checksum mismatch for ${INSTALLER_FILENAME}." >&2
return 1
fi
echo "Checksum for ${INSTALLER_FILENAME} is valid."
return 0
}
gpg --keyserver keys.openpgp.org --recv-keys C6C265324BBEBDC350B513D02D2CEF1034921684
if [ ! -f "${INSTALLER_PATH}" ] || ! verify_cmake_installer; then
rm -f "${CMAKE_CACHE_DIR}/*"
curl -fsSL --retry 3 -o "${INSTALLER_PATH}" "${BASE_URL}/${INSTALLER_FILENAME}"
curl -fsSL --retry 3 -o "${SHA_PATH}" "${BASE_URL}/${SHA_FILENAME}"
curl -fsSL --retry 3 -o "${ASC_PATH}" "${BASE_URL}/${ASC_FILENAME}"
if ! verify_cmake_installer; then
echo "Verification of the downloaded installer failed. Cleaning cache." >&2
rm -f "${CMAKE_CACHE_DIR}/*"
exit 1
fi
fi
sh "${INSTALLER_PATH}" --skip-license --prefix=/usr/local
}
install_python() {
set -e
PYTHON_VERSION=$(grep -E '^python,' /clice/config/default-toolchain-version | cut -d',' -f2)
curl -LsSf https://astral.sh/uv/install.sh | sh
uv python install "${PYTHON_VERSION}"
uv sync
}
do_install() {
set -e
cd /clice
export PATH="/root/.local/bin:${PATH}"
echo "export XMAKE_ROOT=y" >> ~/.bashrc
install_cmake &
install_xmake &
install_python &
for job in $(jobs -p); do
wait $job || exit 1
done
}
do_install
BASH
# download compile dependencies
FROM build-tool-stage AS dependency-cache-stage
# passed from build arg
# "lto" or "non_lto"
ARG BUILD_SRC
# ARG LTO_TYPE=""
RUN --mount=type=bind,src=./,target=/clice,rw \
bash -eux - <<'BASH'
# cache_xmake_packages() {
# set -e
# export PATH="/root/.local/bin:${PATH}"
# export XMAKE_ROOT=y
# LTO_FLAG=""
# if [ "$LTO_TYPE" = "lto" ]; then
# LTO_FLAG="--release"
# fi
# xmake f -y -v --mode=release ${LTO_FLAG}
# xmake f -y -v --mode=debug ${LTO_FLAG}
# }
do_prepare_dependency() {
set -e
cd /clice
# cache_xmake_packages &
for job in $(jobs -p); do
wait $job || exit 1
done
}
do_prepare_dependency
BASH
FROM dependency-cache-stage AS final
RUN bash -eux - <<'BASH'
set -e
# clice is mounted here, so remove everything to reduce image size
rm -rf /clice
# disable git exception in cmake build when Fetch-Content
git config --global --add safe.directory '*'
BASH
WORKDIR /clice
CMD ["/bin/bash"]