mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
devcontainer: Run local customization hooks through the container's life
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.
This commit is contained in:
@@ -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/
|
||||
@@ -0,0 +1 @@
|
||||
/state/
|
||||
+15
-16
@@ -61,24 +61,23 @@ RUN --mount=type=bind,source=install_glab.sh,target=/root/install_glab.sh \
|
||||
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 scripts. They are 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`.
|
||||
# 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 each hook runs and whose home it writes to, so that a hook may spell a
|
||||
# path relative to either.
|
||||
WORKDIR /root
|
||||
RUN --mount=type=bind,source=hooks,target=/opt/cmake-dev-hooks \
|
||||
if test -f /opt/cmake-dev-hooks/root.sh; then \
|
||||
HOME=/root sh -e /opt/cmake-dev-hooks/root.sh; \
|
||||
fi
|
||||
|
||||
# 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=hooks,target=/opt/cmake-dev-hooks \
|
||||
if test -f /opt/cmake-dev-hooks/user.sh; then \
|
||||
HOME=/home/${USERNAME} sh -e /opt/cmake-dev-hooks/user.sh; \
|
||||
fi
|
||||
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
|
||||
|
||||
@@ -30,7 +30,43 @@
|
||||
"target": "/home/cmake-dev/.config/glab-cli"
|
||||
}
|
||||
],
|
||||
"postAttachCommand": "${containerWorkspaceFolder}/.devcontainer/setup-status.sh",
|
||||
// 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": [
|
||||
|
||||
Executable
+47
@@ -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
|
||||
Reference in New Issue
Block a user