Merge topic 'devcontainer'

f1b6b3a6f0 devcontainer: Run local customization hooks through the container's life
3cfbea10e4 devcontainer: Name the workspace path explicitly
f8f4dc7f43 gitlab-ci: add a job to verify the devcontainer in CI
ea0667e779 ci: add scripts to run and verify the devcontainer
9c17332601 devcontainer: Add `clang-tidy`, `clang-tools`, and `clazy`
a8872df649 devcontainer: Add `clang`, `gfortran`, and `valgrind`
8bd053379f devcontainer: Reword the comment on caching package downloads
7ae5c3cfb2 devcontainer: Add the Kitware APT repository
...

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !12423
This commit is contained in:
Brad King
2026-09-11 08:36:11 -04:00
committed by Kitware Robot
22 changed files with 1110 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# The image build sends this directory to the container engine as its build
# context. The state hooks keep at run time is of no use to a build and,
# having been written inside the container, may well be owned by a user the
# build cannot even read it as. Leave it behind.
#
# See `Help/dev/devcontainer.rst`.
state/
+1
View File
@@ -0,0 +1 @@
/state/
+83
View File
@@ -0,0 +1,83 @@
# syntax=docker/dockerfile:1
# Base image for the CMake development container.
#
# The images our CI infrastructure uses, described under `.gitlab/ci/docker/`,
# are built on the distributions we test CMake against and are minimized for
# CI use. Prepare the environment from scratch on Ubuntu instead: it offers
# the broadest ecosystem of packages and tooling for development, including
# the `clang-format` version our style rules require and the Kitware APT
# repository through which CMake itself is published.
ARG BASE_IMAGE=ubuntu:26.04
FROM ${BASE_IMAGE}
ARG USERNAME=cmake-dev
ARG USER_UID=1000
ARG USER_GID=${USER_UID}
# The SHA-256 of the signing key `apt.kitware.com` publishes. Kitware rotates
# that key every few years; updating this hash both approves the new key and
# forces the step below to fetch it again rather than reuse a cached layer.
# Read the current value with:
# curl -fsSL https://apt.kitware.com/keys/kitware-archive-latest.asc | sha256sum
ARG KITWARE_PUBLIC_KEY_SHA256=801bc629e356c3c96f184351272914222ce427777400fa7d1baed3ab180b3e3b
# Add the Kitware APT repository, which carries CMake releases newer than the
# ones the distribution provides, before installing anything from it below.
RUN --mount=type=bind,source=install_kitware_archive.sh,target=/root/install_kitware_archive.sh \
--mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
sh /root/install_kitware_archive.sh ${KITWARE_PUBLIC_KEY_SHA256}
# Install the packages needed to build CMake, run its test suite, build its
# documentation, and satisfy its style rules, along with a few more that make
# the container a comfortable place to work.
#
# Cache the package lists and the downloaded archives, and hide the
# `docker-clean` configuration the base image provides so that it does not
# discard them. A rebuild then downloads only what has changed since the
# last build.
RUN --mount=type=bind,source=install_deps.sh,target=/root/install_deps.sh \
--mount=type=bind,source=deps_packages.lst,target=/root/deps_packages.lst \
--mount=type=bind,source=dev_packages.lst,target=/root/dev_packages.lst \
--mount=type=bind,source=docker-clean,target=/etc/apt/apt.conf.d/docker-clean \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
sh /root/install_deps.sh
# Install the GitLab CLI, `glab`, and `glab-axi`, an agent-ergonomic wrapper
# around it, for interacting with our GitLab instance. Keep the packages
# `npm` downloads in a cache, and unpack the `glab` release in a `tmpfs`, so
# that neither leaves anything behind in the image.
RUN --mount=type=bind,source=install_glab.sh,target=/root/install_glab.sh \
--mount=type=cache,target=/root/.npm,sharing=locked \
--mount=type=tmpfs,target=/tmp \
sh /root/install_glab.sh
# Create an unprivileged user matching the typical host account so that files
# created in the mounted source tree are not owned by `root`.
RUN --mount=type=bind,source=create_user.sh,target=/root/create_user.sh \
sh /root/create_user.sh ${USERNAME} ${USER_UID} ${USER_GID}
# Run the optional local customization hook for the build, if the developer
# has written one. It is ignored by Git so that developers may customize the
# container without modifying tracked files or risking that the customizations
# end up in a commit. See `Help/dev/devcontainer.rst`.
#
# It runs as the container user, who may `sudo`, rather than as `root`: one
# hook that can reach either is simpler to write against than two that each
# reach one. Mount it beside the dispatcher that runs it, under the same
# parent it has in the source tree, so that the dispatcher locates it here the
# same way it does when a container lifecycle command runs it.
#
# `USER` sets neither the working directory nor `HOME`, and BuildKit passes a
# `RUN` only the environment the image records, which names just `PATH`. Say
# where the hook runs and whose home it writes to, so that it may spell a path
# relative to either.
USER ${USERNAME}
WORKDIR /home/${USERNAME}
RUN --mount=type=bind,source=run-hooks.sh,target=/opt/cmake-dev/run-hooks.sh \
--mount=type=bind,source=hooks,target=/opt/cmake-dev/hooks \
HOME=/home/${USERNAME} sh /opt/cmake-dev/run-hooks.sh build
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# Create the unprivileged user that the container runs as, given its name,
# uid, and gid. See `Help/dev/devcontainer.rst`.
set -e
readonly username="$1"
readonly uid="$2"
readonly gid="$3"
# The base image already ships an unprivileged user, which usually occupies
# the uid we want. Remove whoever holds it, and the group holding our gid,
# before creating ours.
if getent passwd "$uid" > /dev/null; then
userdel --remove "$(getent passwd "$uid" | cut -d: -f1)"
fi
if getent group "$gid" > /dev/null; then
groupdel "$(getent group "$gid" | cut -d: -f1)"
fi
groupadd --gid "$gid" "$username"
useradd --uid "$uid" --gid "$gid" --create-home --shell /bin/bash "$username"
# Let the user administer the container, e.g. to install more packages.
echo "$username ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$username"
chmod 0440 "/etc/sudoers.d/$username"
# Pre-create the directories that `devcontainer.json` mounts volumes over so
# that the volumes inherit the ownership recorded here. Name the parents too:
# `install -d` records the ownership only of the directories it is given, and
# tools that write elsewhere under them need to own them as well.
install -d -o "$username" -g "$username" \
"/home/$username/.cache" \
"/home/$username/.cache/ccache" \
"/home/$username/.config" \
"/home/$username/.config/glab-cli" \
"/home/$username/workspace"
+126
View File
@@ -0,0 +1,126 @@
# Packages needed to build CMake, run its test suite, and build its
# documentation.
#
# This list mirrors `.gitlab/ci/docker/debian13-x86_64/deps_packages.lst`,
# section by section, so that the two are easy to compare as our
# dependencies evolve. Package names differ where Ubuntu names them
# differently, and entries needed only by our CI infrastructure are left out
# with a note saying so.
#
# Additions for personal use belong in `.devcontainer/hooks/root.sh`.
# See `Help/dev/devcontainer.rst`.
locales
# Install build requirements.
libssl-dev
# Install development tools.
g++
curl
git
# The base image carries no certificate store, which `curl` and `apt` need
# to reach anything over HTTPS.
ca-certificates
# Install optional external build dependencies.
libarchive-dev
libbz2-dev
libcurl4-gnutls-dev
libexpat1-dev
libjsoncpp-dev
liblzma-dev
libncurses-dev
librhash-dev
libuv1-dev
libzstd-dev
zlib1g-dev
# NOTE `include-what-you-use` is not provided here. CI builds it from
# source, which is more than a development container needs.
# Tools needed for the test suite.
jq
# Packages needed to test CTest.
brz
cvs
subversion
mercurial
# Install ASM_NASM language toolchain.
nasm
# NOTE The HIP language toolchain, `hipcc`, is not provided here. It is
# large and rarely needed while working on CMake itself.
# NOTE The IAR compiler is not provided here, so neither are the packages it
# depends on.
# Packages needed to test find modules.
alsa-utils
aspell
aspell-en
doxygen graphviz
freeglut3-dev
libgnutls28-dev
libarchive-dev
libaspell-dev
libblas-dev
libboost-dev
libboost-filesystem-dev
libboost-program-options-dev
libboost-python-dev
libboost-thread-dev
libbz2-dev
libcups2-dev
libcurl4-gnutls-dev
libdevil-dev
libfontconfig1-dev
libfreetype-dev
libgdal-dev
libgif-dev
libgl1-mesa-dev
libglew-dev
libgmock-dev
libgrpc++-dev libgrpc-dev
libgsl-dev
libgtest-dev
libgtk2.0-dev
libhdf5-dev
libhdf5-mpich-dev
libhdf5-openmpi-dev
libicu-dev
libinput-dev
libjpeg-dev
libjsoncpp-dev
liblapack-dev
liblzma-dev
libmagick++-dev
libopenal-dev
libopenmpi-dev openmpi-bin
libosp-dev
libpng-dev
libpq-dev postgresql-server-dev-18
libprotobuf-dev libprotobuf-c-dev libprotoc-dev protobuf-compiler protobuf-compiler-grpc
libsdl1.2-dev
libsqlite3-dev
libtiff-dev
libuv1-dev
libwxgtk3.2-dev
libx11-dev
libxalan-c-dev
libxerces-c-dev
libxml2-dev libxml2-utils
libxslt1-dev xsltproc
openjdk-25-jdk
python3 python3-dev python3-numpy pypy3 pypy3-dev python3-venv
qtbase5-dev qtbase5-dev-tools
rbenv ruby-build
ruby ruby-dev
swig
unixodbc-dev
# NOTE The packages needed to test ironpython are not provided here. Ubuntu
# no longer carries `libmono-system-windows-forms4.0-cil`.
+70
View File
@@ -0,0 +1,70 @@
# Packages that make the container a comfortable place to work in, but that
# building CMake, running its test suite, and building its documentation do
# not need, and so are not part of `deps_packages.lst`.
#
# Additions for personal use belong in `.devcontainer/hooks/root.sh`.
# See `Help/dev/devcontainer.rst`.
# Make the shell pleasant to work in interactively. `unminimize` restores
# the documentation of the packages the base image provides, which it ships
# without; see `Help/dev/devcontainer.rst`.
bash-completion
less
man-db
manpages
manpages-dev
nano
unminimize
vim
# Build CMake. `g++` comes from `deps_packages.lst`, as CI builds with it;
# `clang` is here for developers who prefer to build with it instead.
#
# `gfortran` is named only to keep the Fortran-only `gcc-16` build that
# Ubuntu 26.04 carries out of the container: `libopenmpi-dev` lists
# `gfortran-16` first among the alternatives it accepts, and Clang, which
# selects the newest GCC installation it finds, cannot link against one that
# provides no C++ standard library.
ccache
clang
cmake
gfortran
ninja-build
# Debug and diagnose CMake. The sanitizer runtimes come with the compilers,
# so building with `-fsanitize=` needs nothing installed here.
gdb
valgrind
# Analyze CMake. `clazy` is the compiler our Clazy CI job builds with,
# `clang-tools` provides `scan-build`, and `clang-tidy` runs the checks in
# `.clang-tidy`. Name the version of `clang-tidy` our checks are written
# against: `Utilities/ClangTidyModule` tracks the Clang release our CI image
# carries, and older versions report `cmake-use-cmsys-fstream` where they
# should not. Ubuntu's unversioned `clang-tidy` package is a release behind.
# `clazy` and `clang-tools` are whatever versions Ubuntu carries, so expect
# their diagnostics to differ from those jobs'. CMake's own checks are not
# among them either way: `CMake_USE_CLANG_TIDY_MODULE` needs
# `Utilities/ClangTidyModule` built against Clang's development files, which
# are not installed here.
clang-tidy-22
clang-tools
clazy
# Build the documentation.
python3-sphinx
# Satisfy our style rules. Our `.clang-format` requires `clang-format`
# version 18, exactly, so install that version by name. Do not install the
# unversioned `clang-format` package: it provides a much newer version.
clang-format-18
pre-commit
# Run `glab` and `glab-axi`, and reach GitLab over SSH. `nodejs` and `npm`
# are needed both to install `glab-axi` and to run it.
nodejs
npm
openssh-client
# Let the unprivileged container user install more packages.
sudo
+85
View File
@@ -0,0 +1,85 @@
{
"name": "CMake",
"build": {
"dockerfile": "Dockerfile"
},
"remoteUser": "cmake-dev",
// Name the workspace path explicitly rather than take the default a tool
// picks for itself, so that the path is the same under every tool and in
// the CI job of `.gitlab/ci/devcontainer-run.sh`, which mounts the source
// tree itself. `create_user.sh` creates the directory and gives it to the
// container user.
"workspaceFolder": "/home/cmake-dev/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/cmake-dev/workspace,type=bind",
"containerEnv": {
"GITLAB_HOST": "gitlab.kitware.com"
},
"remoteEnv": {
"GITLAB_CLIENT_ID": "${localEnv:GITLAB_CLIENT_ID}",
"GITLAB_TOKEN": "${localEnv:GITLAB_TOKEN}"
},
"mounts": [
{
"type": "volume",
"source": "cmake-dev-ccache",
"target": "/home/cmake-dev/.cache/ccache"
},
{
"type": "volume",
"source": "cmake-dev-glab-cli",
"target": "/home/cmake-dev/.config/glab-cli"
}
],
// Run the optional local customization hooks. Each phase is a no-op unless
// the developer has written that hook. See `Help/dev/devcontainer.rst`.
//
// Each is written as a named entry, the form that runs entries concurrently
// and says which one is speaking. Only `postAttachCommand` has two, but
// naming the hook everywhere keeps its output labeled the same way, and
// leaves room for a second entry beside it.
//
// `initializeCommand` runs on the host, so unlike the others it needs a
// POSIX shell there; naming `sh` explicitly, in the form that starts no
// shell of its own, is what makes that work outside a Unix host.
"initializeCommand": {
"hooks": [
"sh",
"${localWorkspaceFolder}/.devcontainer/run-hooks.sh",
"initialize"
]
},
"postCreateCommand": {
"hooks": [
"${containerWorkspaceFolder}/.devcontainer/run-hooks.sh",
"post-create"
]
},
"postStartCommand": {
"hooks": [
"${containerWorkspaceFolder}/.devcontainer/run-hooks.sh",
"post-start"
]
},
"postAttachCommand": {
"setup-status": ["${containerWorkspaceFolder}/.devcontainer/setup-status.sh"],
"hooks": [
"${containerWorkspaceFolder}/.devcontainer/run-hooks.sh",
"post-attach"
]
},
"customizations": {
"vscode": {
"extensions": [
"EditorConfig.EditorConfig",
"ms-vscode.cmake-tools",
"ms-vscode.cpptools",
"lextudio.restructuredtext",
"llvm-vs-code-extensions.vscode-clangd"
],
"settings": {
"C_Cpp.clang_format_path": "/usr/bin/clang-format-18",
"cmake.configureOnOpen": false
}
}
}
}
View File
+5
View File
@@ -0,0 +1,5 @@
# Local customizations applied while building the development container.
# Everything here is ignored, except this file, so that customizations may
# bring along whatever files they need. See `Help/dev/devcontainer.rst`.
/*
!/.gitignore
+30
View File
@@ -0,0 +1,30 @@
#!/bin/sh
# Install the packages listed in `deps_packages.lst` and `dev_packages.lst`.
# See `Help/dev/devcontainer.rst`.
set -e
# Install without asking questions, e.g. the time zone `tzdata` wants.
export DEBIAN_FRONTEND=noninteractive
# Unlike our CI images, keep the documentation that packages carry: the base
# image excludes man pages and the like, which is not what one wants in a
# development environment. Packages already installed in the base image
# remain without their documentation; `unminimize` restores that.
rm -f /etc/dpkg/dpkg.cfg.d/excludes
apt-get update
apt-get install -y $(grep -h '^[^#]\+$' /root/deps_packages.lst /root/dev_packages.lst)
# Add locales, the way our CI images do, for the tests that need them.
sed -i -E '/^# en_US[ .](ISO-8859-1|UTF-8)( |$)/ s/^# //' /etc/locale.gen
dpkg-reconfigure --frontend=noninteractive locales
# `Utilities/Scripts/clang-format.bash` finds `clang-format-18` by name, but
# make the unversioned name resolve to version 18 as well so that tools
# looking for it get the version our style rules require. `CMakeLists.txt`
# searches for `clang-tidy` only under the unversioned name, so the version
# installed by name needs one too.
ln -s "$(command -v clang-format-18)" /usr/local/bin/clang-format
ln -s "$(command -v clang-tidy-22)" /usr/local/bin/clang-tidy
+43
View File
@@ -0,0 +1,43 @@
#!/bin/sh
# Install the GitLab CLI, `glab`, and `glab-axi`, an agent-ergonomic wrapper
# around it. Neither is provided by the distribution.
# See `Help/dev/devcontainer.rst`.
set -e
readonly glab_version="1.114.0"
readonly glab_axi_version="0.6.0"
case "$(uname -m)" in
x86_64)
arch="amd64"
sha256sum="00e892a80d586a1e8b8fdc035321923db99dce0caa3b0c4fd72c5337ffdb1c48"
;;
aarch64)
arch="arm64"
sha256sum="d34d7ddb96ce5e5f3423d7e8053cb14c36bd93984e4b96320f7e20a341b83498"
;;
*)
echo "Unsupported architecture: $(uname -m)" >&2
exit 1
;;
esac
readonly filename="glab_${glab_version}_linux_${arch}.tar.gz"
readonly baseurl="https://gitlab.com/gitlab-org/cli/-/releases/v${glab_version}/downloads"
cd /tmp
curl -L -o "$filename" "$baseurl/$filename"
echo "$sha256sum $filename" > glab.sha256sum
sha256sum --check glab.sha256sum
tar -C /usr/local -xzf "$filename" bin/glab
rm "$filename" glab.sha256sum
# Enable shell completion for interactive use.
mkdir -p /etc/bash_completion.d
/usr/local/bin/glab completion --shell bash > /etc/bash_completion.d/glab
# `glab-axi` is distributed only through npm. Its command surface is
# documented at https://axi.md.
npm install --global --no-audit --no-fund "glab-axi@${glab_axi_version}"
+56
View File
@@ -0,0 +1,56 @@
#!/bin/sh
# Add the Kitware APT repository, which carries CMake releases newer than the
# ones the distribution provides. See `Help/dev/devcontainer.rst`.
set -e
readonly key_sha256="$1"
if test -z "$key_sha256"; then
echo "usage: $0 <sha256-of-kitware-archive-key>" >&2
exit 1
fi
# Install without asking questions.
export DEBIAN_FRONTEND=noninteractive
# `VERSION_CODENAME` names the suite the repository provides for the
# distribution the container is based on.
. /etc/os-release
readonly sources=/etc/apt/sources.list.d/kitware.sources
readonly keyring=/usr/share/keyrings/kitware-archive-keyring
readonly key_url=https://apt.kitware.com/keys/kitware-archive-latest.asc
# Describe the repository, verified with the keyring named as the argument.
write_sources() {
cat > "$sources" <<EOF
Types: deb
URIs: https://apt.kitware.com/ubuntu/
Suites: ${VERSION_CODENAME}
Components: main
Signed-By: $1
EOF
}
apt-get update
# The base image carries neither `curl` nor a certificate store, and neither
# the key nor the repository can be reached without one: both redirect HTTP
# to HTTPS.
apt-get install -y ca-certificates curl
# Trust the repository with the key it publishes, checked against the hash our
# caller pins, just long enough to install `kitware-archive-keyring`. Once
# that package provides the key, `apt` follows the rotations Kitware makes to
# it each year, which a pinned hash would not. `apt` reads an armored key
# only from a `.asc` file, and the package provides a `.gpg` one, so name the
# file each step uses accordingly.
curl -fsSL -o "$keyring.asc" "$key_url"
echo "$key_sha256 $keyring.asc" | sha256sum --check
write_sources "$keyring.asc"
apt-get update
apt-get install -y kitware-archive-keyring
write_sources "$keyring.gpg"
rm "$keyring.asc"
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
# Run the optional local customization hook for one phase of the development
# container's life, named as the sole argument, if the developer has written
# one. See `Help/dev/devcontainer.rst`.
#
# The hooks live beside this script, in a directory Git ignores in its
# entirety, so customizations never appear in a commit and survive updates to
# the tracked container definition.
set -eu
readonly phase="$1"
readonly devcontainer_dir="$(cd -- "$(dirname -- "$0")" && pwd)"
readonly hooks_dir="$devcontainer_dir/hooks"
readonly hook="$hooks_dir/$phase.sh"
test -f "$hook" || exit 0
# Tell the hook where its own directory is, so that a hook needing a file it
# brought along need not work out where it was installed.
CMAKE_DEVCONTAINER_HOOKS_DIR="$hooks_dir"
export CMAKE_DEVCONTAINER_HOOKS_DIR
# A failed `build` hook fails the image build: the image must be reproducible,
# and a customization that did not apply would leave it quietly wrong. Every
# other phase runs against a container that already exists, where the same
# strictness would turn a typo in a personal hook into an environment its
# author can no longer open in order to fix it. Report and carry on instead.
if test "$phase" = build; then
# The build sees this directory through a read-only bind mount, and keeps
# nothing a later phase could read back: whatever this hook writes it
# writes into the image. So there is no state directory to offer it.
exec sh -e "$hook"
fi
# Every other phase runs against the bind-mounted source tree, where a hook
# may keep state that outlives the container. It sits beside the hooks rather
# than among them: the hooks are written by hand and worth carrying to another
# clone, while this is written by whatever they start and worth carrying
# nowhere. `.dockerignore` also leaves it out of the build context, which a
# hook writing here as `root` would otherwise make unreadable to the build.
CMAKE_DEVCONTAINER_STATE_DIR="$devcontainer_dir/state"
export CMAKE_DEVCONTAINER_STATE_DIR
mkdir -p "$CMAKE_DEVCONTAINER_STATE_DIR"
sh -e "$hook" || echo "run-hooks.sh: $phase hook failed; continuing" >&2
+58
View File
@@ -0,0 +1,58 @@
#!/bin/sh
# Report whether this clone and its development container are ready to use
# and, if they are not, print what remains to be set up. Run each time a
# tool attaches to the container. See `Help/dev/devcontainer.rst`.
set -u
cd "$(dirname "$0")/.."
# Check that development setup is up-to-date, the way our `pre-commit` hook
# does. `Utilities/SetupForDevelopment.sh` is interactive, so the container
# cannot run it on the developer's behalf.
eval "$(grep '^SetupForDevelopment_VERSION=' Utilities/SetupForDevelopment.sh)"
setup_done=$(git config --get hooks.SetupForDevelopment || echo 0)
if test "$setup_done" -lt "${SetupForDevelopment_VERSION:-0}"; then
cat <<MESSAGE
git: this work tree is not set up for development.
Run 'Utilities/SetupForDevelopment.sh' to configure your Git identity and
install the project's commit hooks. The work tree is shared with the host,
so running it here sets up both.
MESSAGE
fi
readonly host="${GITLAB_HOST:-gitlab.com}"
if glab auth status --hostname "$host" > /dev/null 2>&1; then
exit 0
fi
# The OAuth flows need the application ID of an OAuth application registered
# on the instance. Offer them only when one is available, and prefer the
# device flow because it needs no redirect back into the container.
if test -n "${GITLAB_CLIENT_ID:-}"; then
readonly login="glab auth login --hostname $host --device"
readonly hint=""
else
readonly login="glab auth login --hostname $host"
readonly hint="
Setting GITLAB_CLIENT_ID, on the host, to the application ID of a GitLab
OAuth application offers to sign in through that application instead.
"
fi
cat <<MESSAGE
glab: no credential for $host yet.
Set one up in either of two ways:
* Run this in the container, which stores the credential under
~/.config/glab-cli, on a volume that persists across rebuilds:
$login
* Or set GITLAB_TOKEN, on the host, to a GitLab personal access token
before starting the container. It is passed through automatically.
$hint
Run '.devcontainer/setup-status.sh' here in the container to check again.
MESSAGE
+1
View File
@@ -3,6 +3,7 @@
.clang-format export-ignore
.clang-tidy export-ignore
.codespellrc export-ignore
.devcontainer export-ignore
.editorconfig export-ignore
.pre-commit-config.yaml export-ignore
.typos.toml export-ignore
+10
View File
@@ -193,6 +193,16 @@ l:clazy-fedora44:
- .rules
needs: []
t:devcontainer-fedora44:
extends:
- .fedora44
- .cmake_test_devcontainer
- .linux_x86_64_priv_tags
- .rules
variables:
CMAKE_CI_JOB_NIGHTLY: "true"
needs: []
# Coverage builds
b:fedora44-gcc-gcov:
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh
set -e
# This job runs in a container whose filesystem is `overlayfs`, over which
# `podman` cannot stack its own `overlay` storage driver:
#
# 'overlay' is not supported over overlayfs, a mount_program is required
#
# `vfs` copies each layer rather than stacking it, which is slower but asks
# nothing of the filesystem underneath. Name it in the environment so that
# every `podman` command below addresses the same storage, the build included.
export STORAGE_DRIVER=vfs
readonly dockerfile=".devcontainer/Dockerfile"
readonly name="cmake-dev-container"
# Build the container image from the devcontainer Dockerfile
echo "# Building devcontainer image"
podman build -t "$name" -f "$dockerfile" .devcontainer
# Ensure named volumes exist for ccache and glab-cli cache persistence
echo "# Ensuring volumes exist"
podman volume create cmake-dev-ccache 2>/dev/null || true
podman volume create cmake-dev-glab-cli 2>/dev/null || true
# Run the container with volumes mounted.
#
# The job's own container has no cgroup controllers delegated to it, so
# `podman` cannot place the container it starts under one:
#
# crun: controller `pids` is not available under /sys/fs/cgroup/...
#
# Ask for no cgroup at all. Verification needs no resource limits, and there
# is nothing to limit them with. There is no terminal either, so do not ask
# for one.
echo "# Starting devcontainer"
podman run --rm \
--cgroups=disabled \
-v "$PWD:/home/cmake-dev/workspace:Z" \
-v cmake-dev-ccache:/home/cmake-dev/.cache/ccache:Z \
-v cmake-dev-glab-cli:/home/cmake-dev/.config/glab-cli:Z \
--workdir /home/cmake-dev/workspace \
"$name" \
.gitlab/ci/devcontainer-verify.sh
+75
View File
@@ -0,0 +1,75 @@
#!/bin/sh
set -e
# Source CI environment scripts
. .gitlab/ci/env.sh
# Verify devcontainer volumes are accessible
echo "# Verifying devcontainer volumes"
# Check ccache volume
if test -d /home/cmake-dev/.cache/ccache; then
echo "ccache volume found at /home/cmake-dev/.cache/ccache"
ccache -s 2>/dev/null || echo "ccache stats unavailable (may be fresh)"
else
echo "WARNING: ccache volume not found at expected path"
fi
# Check glab-cli volume
if test -d /home/cmake-dev/.config/glab-cli; then
echo "glab-cli volume found at /home/cmake-dev/.config/glab-cli"
else
echo "WARNING: glab-cli volume not found at expected path"
fi
# Verify cmake is available
echo "# Verifying cmake"
cmake --version
# Verify ccache is available
echo "# Verifying ccache"
ccache --version
# Build a simple test project to verify the devcontainer works
echo "# Building test project"
# Create a temporary build directory
mkdir -p /tmp/devcontainer-test
cd /tmp/devcontainer-test
# Create a minimal C project
cat > CMakeLists.txt <<cmake
cmake_minimum_required(VERSION 3.15)
project(devcontainer-test C)
add_executable(test
main.c
)
cmake
cat > main.c <<cmake
#include <stdio.h>
int main(void) {
printf("Devcontainer CMake build OK\n");
return 0;
}
cmake
# Configure with cmake using ccache cache path
echo "# Configuring with cmake"
cmake 2>&1 \
-GNinja \
-S. \
-Bbuild
# Build
echo "# Building"
ninja -C build 2>&1
# Verify cache was preserved
echo "# Verifying ccache hit"
ccache -s 2>/dev/null | head -5 || true
echo "# Devcontainer verification complete"
+15
View File
@@ -657,6 +657,13 @@
- docker
- linux-x86_64
.linux_x86_64_priv_tags:
tags:
- cmake
- docker
- linux-x86_64
- privileged
.linux_x86_64_v3_tags:
tags:
- cmake
@@ -969,3 +976,11 @@
-DCMake_SPHINX_CMAKE_ORG_OUTDATED=$CMAKE_CI_SPHINX_OUTDATED
-DCMake_VERSION_NO_GIT=$CMAKE_CI_VERSION_NO_GIT
- ninja
### Devcontainer testing
.cmake_test_devcontainer:
stage: test
script:
- dnf install -y --setopt=install_weak_deps=False podman
- .gitlab/ci/devcontainer-run.sh
+3
View File
@@ -26,6 +26,8 @@ To contribute patches:
#. Fork the upstream `CMake Repository`_ into a personal account.
#. Run `Utilities/SetupForDevelopment.sh`_ for local git configuration.
#. See `Building CMake`_ for building CMake locally.
Optionally, see the `CMake Dev Container Guide`_ for a ready-made
development environment.
#. See the `CMake Source Code Guide`_ for coding guidelines
and the `CMake Testing Guide`_ for testing instructions.
#. Create a topic branch named suitably for your work.
@@ -52,6 +54,7 @@ preparing or submitting a change.
.. _`CMake Repository`: https://gitlab.kitware.com/cmake/cmake
.. _`Utilities/SetupForDevelopment.sh`: Utilities/SetupForDevelopment.sh
.. _`Building CMake`: README.rst#building-cmake
.. _`CMake Dev Container Guide`: Help/dev/devcontainer.rst
.. _`CMake Source Code Guide`: Help/dev/source.rst
.. _`CMake Testing Guide`: Help/dev/testing.rst
.. _`commit messages`: Help/dev/review.rst#commit-messages
+2
View File
@@ -36,6 +36,7 @@ Developer Documentation
CMake developer documentation is provided by the following documents:
* The `CMake Dev Container Guide`_.
* The `CMake Source Code Guide`_.
* The `CMake Documentation Guide`_.
* The `CMake Testing Guide`_.
@@ -43,6 +44,7 @@ CMake developer documentation is provided by the following documents:
* The `CMake Debugging Guide`_.
* The `CMake Diagnostics Guide`_.
.. _`CMake Dev Container Guide`: devcontainer.rst
.. _`CMake Source Code Guide`: source.rst
.. _`CMake Documentation Guide`: documentation.rst
.. _`CMake Testing Guide`: testing.rst
+310
View File
@@ -0,0 +1,310 @@
CMake Dev Container Guide
*************************
The following is a guide to the development container provided for building,
testing, and formatting CMake itself. See documentation on `CMake
Development`_ for more information.
.. _`CMake Development`: README.rst
Overview
========
The `.devcontainer`_ directory at the top of the CMake source tree describes
a Linux development environment following the `Dev Container Specification`_.
Using it is entirely optional, but it offers a quick way to get a complete
environment with all the tools needed to build CMake, run its test suite,
build its documentation, and satisfy its style rules.
The container is built on Ubuntu, which offers the broadest ecosystem of
packages and tooling for development. Its package lists mirror those of the
Debian image our CI infrastructure uses, described under
`.gitlab/ci/docker`_, so the dependencies available closely match the ones
against which merge requests are tested. A few pieces of the CI environment
are left out because a development container rarely needs them, and each is
noted in the list that would otherwise carry it:
`.devcontainer/deps_packages.lst`_
The packages needed to build CMake, run its test suite, and build its
documentation.
`.devcontainer/dev_packages.lst`_
The packages that make the container a comfortable place to work in, which
building and testing CMake does not itself need.
.. _`.devcontainer`: ../../.devcontainer
.. _`Dev Container Specification`: https://containers.dev
.. _`.gitlab/ci/docker`: ../../.gitlab/ci/docker
.. _`.devcontainer/deps_packages.lst`: ../../.devcontainer/deps_packages.lst
.. _`.devcontainer/dev_packages.lst`: ../../.devcontainer/dev_packages.lst
Prerequisites
=============
* A container engine such as `Docker`_ or `Podman`_. Building the image uses
bind and cache mounts, as the image builds under `.gitlab/ci/docker`_ do, so
it needs `BuildKit`_, enabled by default since Docker 23.0, or Podman 4.0 or
newer.
* A tool that understands the specification, such as the `Dev Containers`_
extension for Visual Studio Code, the `Dev Container CLI`_, or another
`supporting tool`_.
.. _`Docker`: https://docs.docker.com/get-started/get-docker/
.. _`Podman`: https://podman.io
.. _`BuildKit`: https://docs.docker.com/build/buildkit/
.. _`Dev Containers`: https://code.visualstudio.com/docs/devcontainers/containers
.. _`Dev Container CLI`: https://github.com/devcontainers/cli
.. _`supporting tool`: https://containers.dev/supporting
Usage
=====
In Visual Studio Code, open the CMake source tree and run the
``Dev Containers: Reopen in Container`` command. With the
`Dev Container CLI`_, start the container from the top of the source tree:
.. code-block:: console
$ devcontainer up --workspace-folder .
$ devcontainer exec --workspace-folder . bash
The source tree is mounted into the container, so changes made inside it are
made to the same working tree. Commits may be created either inside or
outside the container. `Utilities/SetupForDevelopment.sh`_ may likewise be
run in either place to configure your Git identity and install the project's
commit hooks, and takes effect in both. It is interactive, so the container
does not run it automatically, but `.devcontainer/setup-status.sh`_ reports
whether it still needs to be run each time a tool attaches to the container.
.. _`Utilities/SetupForDevelopment.sh`: ../../Utilities/SetupForDevelopment.sh
.. _`.devcontainer/setup-status.sh`: ../../.devcontainer/setup-status.sh
Build CMake in the container as one would on any other Linux host, as
described in `Building CMake`_:
.. code-block:: console
$ cmake -G Ninja -B build -S .
$ cmake --build build
$ ctest --test-dir build
.. _`Building CMake`: ../../README.rst#building-cmake
Provided Tools
==============
In addition to the compiler and the external dependencies CMake can build
against, the container provides:
* ``cmake`` and ``ninja``, to build CMake with. ``cmake`` comes from the
`Kitware APT repository`_, which the container configures, so it is the
latest CMake release rather than the older one Ubuntu carries, and
``apt-get`` offers each new release as it is published:
.. code-block:: console
$ sudo apt-get update
$ sudo apt-get install --only-upgrade cmake
The repository also carries release candidates, in a suite named after the
Ubuntu release with ``-rc`` appended. Add it to the ``Suites`` field of
``/etc/apt/sources.list.d/kitware.sources`` to install those as well.
* ``clang``, for developers who would rather build with it than with the
default ``g++``:
.. code-block:: console
$ cmake -G Ninja -B build-clang -S . -DCMAKE_CXX_COMPILER=clang++
* ``ccache``, to speed up repeated builds, e.g.:
.. code-block:: console
$ cmake -G Ninja -B build -S . -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
Its cache is stored in a named volume so that it survives rebuilds of the
container.
* ``clang-format`` version 18, exactly as required by our `C++ Code Style`_,
available as both ``clang-format`` and ``clang-format-18``:
.. code-block:: console
$ Utilities/Scripts/clang-format.bash --modified
* ``pre-commit``, to run the checks configured in
`.pre-commit-config.yaml`_:
.. code-block:: console
$ pre-commit install
$ pre-commit run --all-files
* ``sphinx-build``, to build the documentation as described in the
`CMake Documentation Guide`_.
* ``gdb``, to debug CMake as described in the `CMake Debugging Guide`_.
* ``valgrind``, and the sanitizer runtimes that come with ``g++`` and
``clang``, to run CMake and its tests under a memory checker, the way the
sanitizer and Valgrind jobs of our CI do:
.. code-block:: console
$ cmake -G Ninja -B build-asan -S . \
-DCMAKE_C_FLAGS=-fsanitize=address \
-DCMAKE_CXX_FLAGS=-fsanitize=address
$ cmake --build build-asan
$ ctest --test-dir build-asan
* ``clang-tidy``, ``scan-build``, and ``clazy``, the compiler our Clazy CI
job builds with, to analyze CMake rather than only compile it:
.. code-block:: console
$ cmake -G Ninja -B build-tidy -S . -DCMake_RUN_CLANG_TIDY=ON
$ cmake -G Ninja -B build-clazy -S . -DCMAKE_CXX_COMPILER=clazy
``clang-tidy`` is the version our checks are written against, which is
not the one Ubuntu's unversioned package provides. ``scan-build`` and
``clazy`` are whatever versions Ubuntu carries rather than the ones our
CI image does, so expect their diagnostics to differ from those jobs'.
CMake's own checks are not available either way:
``CMake_USE_CLANG_TIDY_MODULE`` needs `Utilities/ClangTidyModule`_ built
against Clang's development files, which the container does not install.
* ``glab``, the `GitLab CLI`_, to work with merge requests, issues, and
pipelines on our GitLab instance, and `glab-axi`_, a wrapper around it
whose output follows the `AXI`_ conventions:
.. code-block:: console
$ glab mr list
$ glab-axi mr view 1234
See `GitLab Authentication`_ below for the one-time setup they need.
.. _`Kitware APT repository`: https://apt.kitware.com
.. _`C++ Code Style`: source.rst#c-code-style
.. _`.pre-commit-config.yaml`: ../../.pre-commit-config.yaml
.. _`Utilities/ClangTidyModule`: ../../Utilities/ClangTidyModule
.. _`CMake Documentation Guide`: documentation.rst
.. _`CMake Debugging Guide`: debug.rst
.. _`GitLab CLI`: https://docs.gitlab.com/editor_extensions/gitlab_cli/
.. _`glab-axi`: https://github.com/karotkriss/glab-axi
.. _`AXI`: https://axi.md
The base image ships without documentation, but the container keeps the man
pages and other documentation of every package installed on top of it. Run
``sudo unminimize`` to restore the documentation of the packages the base
image itself provides.
GitLab Authentication
=====================
The container sets ``GITLAB_HOST`` to ``gitlab.kitware.com`` so that ``glab``
and ``glab-axi`` address our GitLab instance by default. Both still need a
credential for it. `.devcontainer/setup-status.sh`_ reports whether a
working credential has been configured and provides instructions to do so if
not. It is run automatically when attaching to the container.
A ``GITLAB_TOKEN`` or ``GITLAB_CLIENT_ID`` set on the host is passed through
to the container, so a credential configured outside it is used as-is.
Local Customization
===================
The container is meant to be an unconstrained space that each developer may
adapt. `.devcontainer/run-hooks.sh`_ runs an optional script, if one is
present, at each of five points in the container's life:
``.devcontainer/hooks/initialize.sh``
Runs on the host, before the container is created or started, e.g. to
prepare something the container goes on to use.
``.devcontainer/hooks/build.sh``
Runs while the image is built, e.g. to install additional packages.
``.devcontainer/hooks/post-create.sh``
Runs once, when the container is created, and unlike ``build.sh`` runs with
the source tree mounted, e.g. to prepare something in the work tree itself.
``.devcontainer/hooks/post-start.sh``
Runs each time the container starts, e.g. to start a background service.
Note that a container may be started by a tool that never attaches to it.
``.devcontainer/hooks/post-attach.sh``
Runs each time a tool attaches to the container, concurrently with the
report described under `GitLab Authentication`_ above rather than before or
after it, so expect whatever it prints to interleave with that report.
``build.sh`` runs as the container user, in that user's home directory, rather
than as ``root``; reach for ``sudo`` for whatever needs privilege. One hook
that can be either user is simpler to write against than two that each can be
one. Bear in mind that ``sudo`` resets ``HOME`` to ``root``'s, so pass ``-H``
or ``-E`` where a command cares which home it writes to. The three
container hooks that follow it likewise run as the container user, in the
workspace directory; ``post-start.sh`` and ``post-attach.sh`` run again on
every start and attach, so write those two to be repeatable.
Each hook is given ``CMAKE_DEVCONTAINER_HOOKS_DIR``, naming the ``hooks``
directory itself, so that a hook needing a file it brought along need not work
out where it was installed. Every hook but ``build.sh`` is given
``CMAKE_DEVCONTAINER_STATE_DIR`` as well, a directory to keep runtime state
in: it is part of the source tree, bind-mounted from the host, so what a hook
leaves there outlives the container. It sits beside the ``hooks`` directory
rather than inside it, because the two are worth different things: hooks are
written by hand and worth carrying to another clone, while state is written by
whatever they start and worth carrying nowhere.
`.devcontainer/.dockerignore`_ also keeps it out of the image build context,
which state written as ``root`` would otherwise make unreadable. ``build.sh``
is given neither a state directory nor a writable ``hooks`` directory, because
a build keeps nothing a later phase could read back: whatever it writes, it
writes into the image.
A failing ``build.sh`` fails the image build, because an image whose
customizations did not apply is quietly wrong. The other three are reported
and otherwise ignored: they run against a container that already exists, where
the same strictness would turn a typo into an environment its author can no
longer open in order to fix it.
The whole ``hooks`` directory is ignored by Git, apart from its
``.gitignore``, so customizations never appear in a commit, may bring along
whatever other files they need, and are preserved across updates to the
tracked container definition. For example, to add a package, a shell alias,
and a service that runs for as long as the container does:
.. code-block:: console
$ cat > .devcontainer/hooks/build.sh <<'EOF'
sudo apt-get update && sudo apt-get install -y tmux
echo "alias b='cmake --build build'" >> ~/.bashrc
EOF
$ cat > .devcontainer/hooks/post-start.sh <<'EOF'
pidof my-service > /dev/null ||
my-service --daemon --state "$CMAKE_DEVCONTAINER_STATE_DIR/my-service"
EOF
Rebuild the container to apply a new or changed ``build.sh``, e.g. with the
``Dev Containers: Rebuild Container`` command in Visual Studio Code. The
other three hooks are read afresh each time they run.
``initialize.sh`` is the one hook that runs outside the container, so it is
also the one that depends on the host: it needs ``sh`` on the ``PATH`` there.
That is a given on a Unix host and, on Windows, comes with Git for Windows.
Some things a container needs must be settled before it exists, and so cannot
come from a hook: added capabilities, extra mounts, `Dev Container Features`_,
and arguments to the container engine all belong to
`.devcontainer/devcontainer.json`_. Those, and any larger or longer-lived
change, may of course be made by editing that file or
`.devcontainer/Dockerfile`_ directly, but take care not to commit them
accidentally.
.. _`.devcontainer/run-hooks.sh`: ../../.devcontainer/run-hooks.sh
.. _`.devcontainer/.dockerignore`: ../../.devcontainer/.dockerignore
.. _`Dev Container Features`: https://containers.dev/features
.. _`.devcontainer/Dockerfile`: ../../.devcontainer/Dockerfile
.. _`.devcontainer/devcontainer.json`: ../../.devcontainer/devcontainer.json