-
Notifications
You must be signed in to change notification settings - Fork 0
feat(docker): auto delegate scripts to docker #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
6f11ff5
feat: add env.sh and docker/exec.sh for container delegation
wambitz 6da5660
feat: update all scripts to use env.sh and container delegation
wambitz 02a4a75
fix: set Doxygen OUTPUT_DIRECTORY to docs/generated
wambitz 19e1a83
fix: use absolute paths and portable path resolution in scripts
wambitz af84b8b
fix: use printf for logging and improve SCRIPT_DIR validation in env.sh
wambitz 0c73143
fix: use absolute paths in coverage.sh for invocation from any directory
wambitz 438b2d8
fix: add Docker preflight checks in exec.sh before delegation
wambitz afcefe3
fix: use single find with null-delimited iteration in format.sh
wambitz eaa9ccd
fix: use absolute paths in lint.sh and remove redundant tool check
wambitz 6784943
fix: add sourcing guard to env.sh
wambitz ef14844
fix: propagate clang-tidy failures in lint.sh
wambitz a7aed48
fix: use delegation scripts in CI to fix path mismatch
wambitz 3eb176b
fix: skip Docker delegation in CI and restore apt-get deps
wambitz fa11cb4
docs: add CI and container delegation guide with GHCR upgrade path
wambitz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,7 +36,7 @@ build/ | |
| install/ | ||
|
|
||
| # Doxygen | ||
| html/ | ||
| docs/generated/ | ||
|
|
||
| # Testing | ||
| Testing/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Build & Test Commands | ||
|
|
||
| ```bash | ||
| ./scripts/build.sh # Debug build with tests enabled | ||
| ./scripts/test.sh # Run all tests | ||
| ./scripts/package.sh # Release build + CPack packaging | ||
| ./scripts/format.sh # Apply clang-format | ||
| ./scripts/format.sh --check # Verify formatting (CI mode) | ||
| ./scripts/lint.sh # Run clang-tidy (build first — needs compile_commands.json) | ||
| ./scripts/coverage.sh # Generate lcov coverage report | ||
| ./scripts/docs.sh # Generate Doxygen documentation | ||
| ``` | ||
|
|
||
| Pre-commit hooks run on **pre-push** (not pre-commit): `pre-commit install --hook-type pre-push` | ||
|
|
||
| All scripts auto-delegate to Docker when run outside the container (`docker run --rm`). The delegation logic lives in `scripts/docker/exec.sh`, sourced by each script via `scripts/env.sh`. Inside the container or CI (`CI=true`), scripts run directly with no overhead. See `docs/ci-container-delegation.md` for details on the CI strategy and a GHCR upgrade path. | ||
|
|
||
| ## Architecture | ||
|
|
||
| This is a C++ project template using **CMake >= 3.20** and **C++17**. Each build target lives in its own `src/<name>/` subdirectory with its own `CMakeLists.txt`, registered via `add_subdirectory()` in the root CMakeLists.txt. | ||
|
|
||
| The template demonstrates four library patterns that build on each other: | ||
|
|
||
| - **Static library** (`example_static`) — simplest case, compiled and linked at build time | ||
| - **Shared library** (`example_shared`) — uses RPATH (`$ORIGIN/../lib`) so the binary finds `.so` files relative to itself at runtime, making the install relocatable | ||
| - **Interface library** (`example_interface`) — header-only, no compiled output; uses `INTERFACE` visibility so dependents get the include paths automatically | ||
| - **Public/Private visibility** (`example_public_private`) — demonstrates how `PUBLIC` includes propagate to dependents while `PRIVATE` includes stay internal | ||
|
|
||
| The **plugin system** (`example_plugin_loader` + `example_plugin_impl`) shows runtime loading via `dlopen()`. Plugins implement a C-compatible API defined in `plugin_api.hpp` and must export `create_plugin()` as `extern "C"`. The loader discovers plugin `.so` files via RPATH. | ||
|
|
||
| The main executable (`src/main/`) links against all libraries and demonstrates their usage together. | ||
|
|
||
| Tests use **GoogleTest v1.14.0** (fetched via `FetchContent`). Test files follow the pattern `tests/test_<target_name>.cpp` and are discovered via `gtest_discover_tests()`. The `tests/test_helpers.hpp` provides an `OutputCapture` utility for testing stdout. | ||
|
|
||
| ## Code Style | ||
|
|
||
| Enforced by `.clang-format` and `.clang-tidy` — CI rejects non-conforming code. | ||
|
|
||
| - Google C++ style base, **4-space indent**, **100-char column limit**, K&R braces | ||
| - Pointer alignment: left (`int* ptr`) | ||
| - Naming: `lower_case` for variables/members, `CamelCase` for classes/structs | ||
| - Headers use `#pragma once` (not traditional include guards) | ||
| - Includes: sorted and grouped (main header, then system, then project) | ||
| - Use `target_include_directories` and `target_link_libraries` with correct CMake visibility (PUBLIC/PRIVATE/INTERFACE) — never raw compiler/linker flags | ||
| - Shared libraries must configure RPATH — never hardcode absolute paths |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # CI and Container Delegation | ||
|
|
||
| ## How Script Delegation Works | ||
|
|
||
| All project scripts (`build.sh`, `test.sh`, `format.sh`, `lint.sh`, etc.) auto-delegate to Docker when run outside the container. The delegation logic in `scripts/docker/exec.sh` checks three conditions in order: | ||
|
|
||
| 1. **Inside a container** (`/.dockerenv` exists) — run directly, no delegation | ||
| 2. **CI environment** (`CI=true`) — run directly, tools provided by the runner | ||
| 3. **Developer host** — delegate to Docker via `docker run --rm` | ||
|
|
||
| ``` | ||
| Developer host Container / CI runner | ||
| -------------- --------------------- | ||
| ./scripts/build.sh | ||
| source env.sh | ||
| source docker/exec.sh | ||
| delegate_to_container | ||
| /.dockerenv? No | ||
| CI=true? No | ||
| docker run --rm ... build.sh --> ./scripts/build.sh | ||
| exit $? delegate_to_container | ||
| /.dockerenv? Yes | ||
| return 0 | ||
| cmake / make / ... | ||
| <-- exits | ||
| ``` | ||
|
|
||
| ## Current CI Approach | ||
|
|
||
| The CI workflow installs tools directly on the GitHub Actions runner and skips Docker delegation: | ||
|
|
||
| ```yaml | ||
| # .github/workflows/ci.yml | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Install deps | ||
| run: > | ||
| sudo apt-get update && | ||
| sudo apt-get install -y cmake clang-format clang-tidy | ||
| - name: Format check | ||
| run: ./scripts/format.sh --check | ||
| - name: Build | ||
| run: ./scripts/build.sh | ||
| - name: Lint check | ||
| run: ./scripts/lint.sh | ||
| - name: Test | ||
| run: ./scripts/test.sh | ||
| ``` | ||
|
|
||
| GitHub Actions sets `CI=true` automatically, so `delegate_to_container` returns immediately and scripts run directly on the runner. This is fast, requires no Docker setup, and works out-of-the-box for anyone who forks the template. | ||
|
|
||
| ### Why not Docker in CI? | ||
|
|
||
| An earlier approach ran some CI steps directly on the host and others via Docker delegation. This caused path mismatches: `compile_commands.json` generated on the host contained runner paths (`/home/runner/work/...`), but `clang-tidy` ran inside a Docker container with different paths (`/workspaces/...`), causing crashes. The current approach avoids this by running everything in the same context. | ||
|
|
||
| ## Alternative: GHCR Container Image | ||
|
|
||
| For production projects that require identical toolchains in CI and local development, you can publish the dev container image to GitHub Container Registry (GHCR) and use it as the CI job container. | ||
|
|
||
| GHCR is free for public repositories (unlimited storage and bandwidth). | ||
|
|
||
| ### Setup | ||
|
|
||
| **1. Add a workflow to build and push the image** (`.github/workflows/docker-image.yml`): | ||
|
|
||
| ```yaml | ||
| name: Docker Image | ||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'Dockerfile' | ||
| - 'scripts/docker/entrypoint.sh' | ||
| - '.github/workflows/docker-image.yml' | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
| REGISTRY: ghcr.io | ||
| IMAGE_NAME: ${{ github.repository }}/cpp-dev | ||
|
|
||
| jobs: | ||
| build-and-push: | ||
| runs-on: ubuntu-24.04 | ||
| permissions: | ||
| contents: read | ||
| packages: write | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Log in to GHCR | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Build and push | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: docker/Dockerfile | ||
| push: true | ||
| tags: | | ||
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | ||
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} | ||
| ``` | ||
|
|
||
| **2. Update the CI workflow** to use the published image: | ||
|
|
||
| ```yaml | ||
| name: CI | ||
| on: [push, pull_request] | ||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-24.04 | ||
| container: | ||
| image: ghcr.io/${{ github.repository }}/cpp-dev:latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Format check | ||
| run: ./scripts/format.sh --check | ||
| - name: Build | ||
| run: ./scripts/build.sh | ||
| - name: Lint check | ||
| run: ./scripts/lint.sh | ||
| - name: Test | ||
| run: ./scripts/test.sh | ||
| ``` | ||
|
|
||
| ### Why this works without code changes | ||
|
|
||
| When GitHub Actions runs a job with `container:`, it creates `/.dockerenv` inside the container. The first check in `delegate_to_container` detects this and skips delegation. The `CI=true` check is never reached, so both guards coexist without conflict. | ||
|
|
||
| ### Trade-offs | ||
|
|
||
| | | apt-get (current) | GHCR container | | ||
| |---|---|---| | ||
| | CI speed | Fast | Fast (pre-built image) | | ||
| | Tool consistency | Runner versions (minor drift possible) | Identical to local dev | | ||
| | Fork setup | Zero — works immediately | Must trigger image build first | | ||
| | Maintenance | 1 workflow | 2 workflows | | ||
|
|
||
| ### First-time setup for GHCR | ||
|
|
||
| 1. Push the `docker-image.yml` workflow to `main` | ||
| 2. Go to Actions tab and manually trigger "Docker Image" (`workflow_dispatch`) | ||
| 3. Go to Packages tab and ensure the image visibility matches the repo (public/private) | ||
| 4. Update `ci.yml` to use `container:` as shown above | ||
| 5. Remove the `apt-get install` step (tools come from the image) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.