mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
The container ran a developer's own `hooks/root.sh` and `hooks/user.sh` while the image was built, and nothing of theirs afterwards. A customization that has to start something, or to reach the source tree, had nowhere to run: the tree is not mounted during the build, and the one lifecycle command the configuration used was spoken for by the status report. Run an optional script per phase through `run-hooks.sh`, and offer five: `initialize` on the host, `build` in the image, and `post-create`, `post-start` and `post-attach` in the container. A failing `build` hook fails the image build, because an image whose customizations did not apply is quietly wrong; the rest are reported and otherwise ignored, so that a typo in a personal hook cannot leave its author unable to open the container in order to fix it. The two build hooks become one, running as the container user, who may `sudo`: one hook that reaches either user is simpler to write against than two that each reach one. Every hook is told where it lives, and every hook but the build one is given a directory to keep state in, beside the hooks rather than among them because state is written by whatever they start rather than by hand. `.dockerignore` keeps that directory out of the build context, which state written as `root` would otherwise make unreadable.
311 lines
13 KiB
ReStructuredText
311 lines
13 KiB
ReStructuredText
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
|