Skip to content

Commit d5f524c

Browse files
committed
added pr-docker-image.yml to generate image with PR content, updated README and CONTRIBUTING.md, added log to gemc start
1 parent 0bb5884 commit d5f524c

6 files changed

Lines changed: 285 additions & 34 deletions

File tree

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Build and publish a per-PR multi-arch GHCR image for preview/testing.
2+
#
3+
# Like Deploy, this builds per-arch images (amd64 + arm64) and stitches them into
4+
# a single multi-arch manifest. Unlike Deploy (which covers every OS), this targets
5+
# one OS only: the latest AlmaLinux produced by the ci/tags_config.sh configuration
6+
# (currently almalinux 10). The published tag carries a `pr-<number>` suffix so each
7+
# PR gets its own image. When the PR is closed the image is deleted from GHCR.
8+
name: PR Docker Image
9+
10+
run-name: PR #${{ github.event.pull_request.number }} image
11+
12+
permissions:
13+
contents: read
14+
packages: write
15+
16+
env:
17+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
18+
19+
concurrency:
20+
group: pr-docker-image-${{ github.event.pull_request.number }}
21+
cancel-in-progress: true
22+
23+
on:
24+
pull_request:
25+
types: [ opened, synchronize, reopened, closed ]
26+
27+
jobs:
28+
resolve:
29+
if: ${{ github.event.action != 'closed' }}
30+
name: Resolve build parameters
31+
runs-on: ubuntu-latest
32+
outputs:
33+
image: ${{ steps.cfg.outputs.image }}
34+
gemc_tag: ${{ steps.cfg.outputs.gemc_tag }}
35+
geant4_tag: ${{ steps.cfg.outputs.geant4_tag }}
36+
alma_tag: ${{ steps.cfg.outputs.alma_tag }}
37+
tag: ${{ steps.cfg.outputs.tag }}
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v6
41+
42+
- id: cfg
43+
name: Resolve image ref, tags and latest AlmaLinux
44+
run: |
45+
source ci/tags_config.sh
46+
image="$(build_image_ref)"
47+
gemc_tag="$(get_gemc_tags | awk '{print $1}')"
48+
geant4_tag="$(get_geant4_tags | awk '{print $1}')"
49+
alma_tag="$(get_latest_almalinux)"
50+
tag="${gemc_tag}-almalinux-${alma_tag}-pr-${{ github.event.pull_request.number }}"
51+
{
52+
echo "image=$image"
53+
echo "gemc_tag=$gemc_tag"
54+
echo "geant4_tag=$geant4_tag"
55+
echo "alma_tag=$alma_tag"
56+
echo "tag=$tag"
57+
} >> "$GITHUB_OUTPUT"
58+
echo "Will build and push ${image}:${tag} (amd64 + arm64)"
59+
60+
# Per-arch build jobs. Each pushes an arch-suffixed tag (…-amd64 / …-arm64)
61+
# that the manifest job stitches into the bare PR tag.
62+
build_arch:
63+
if: ${{ github.event.action != 'closed' }}
64+
name: almalinux/${{ needs.resolve.outputs.alma_tag }} ${{ matrix.arch }}
65+
needs: resolve
66+
runs-on: ${{ matrix.runner }}
67+
strategy:
68+
fail-fast: false
69+
matrix:
70+
include:
71+
- arch: amd64
72+
runner: ubuntu-latest
73+
platform: linux/amd64
74+
- arch: arm64
75+
runner: ubuntu-24.04-arm
76+
platform: linux/arm64
77+
env:
78+
DOCKER_BUILD_SUMMARY: false
79+
steps:
80+
- name: Checkout repository
81+
uses: actions/checkout@v6
82+
with:
83+
fetch-depth: 0
84+
85+
- name: Free up disk space
86+
uses: ./.github/actions/free-disk-space
87+
with:
88+
prune_docker: "true"
89+
show_tree: "true"
90+
91+
- name: Set up Buildx
92+
uses: docker/setup-buildx-action@v4
93+
94+
- name: Log in to GHCR
95+
uses: docker/login-action@v4
96+
with:
97+
registry: ghcr.io
98+
username: ${{ github.actor }}
99+
password: ${{ secrets.GITHUB_TOKEN }}
100+
101+
- id: meta
102+
name: Docker metadata (PR arch tag)
103+
uses: docker/metadata-action@v6
104+
with:
105+
images: ${{ needs.resolve.outputs.image }}
106+
tags: |
107+
type=raw,value=${{ needs.resolve.outputs.tag }}-${{ matrix.arch }}
108+
labels: |
109+
org.opencontainers.image.source=${{ github.repository }}
110+
org.opencontainers.image.description=GEMC PR #${{ github.event.pull_request.number }} (almalinux ${{ needs.resolve.outputs.alma_tag }}, ${{ matrix.arch }})
111+
112+
- name: Generate Dockerfile
113+
run: |
114+
python3 ci/dockerfile_creator.py \
115+
-i almalinux \
116+
-t "${{ needs.resolve.outputs.alma_tag }}" \
117+
--gemc-version "${{ needs.resolve.outputs.gemc_tag }}" \
118+
--geant4-version "${{ needs.resolve.outputs.geant4_tag }}" \
119+
--source context \
120+
--package-arch "${{ matrix.arch }}" \
121+
> Dockerfile.generated
122+
cat Dockerfile.generated
123+
124+
- name: Build & Push
125+
uses: docker/build-push-action@v7
126+
with:
127+
pull: true
128+
no-cache: true
129+
context: .
130+
file: ./Dockerfile.generated
131+
target: final
132+
platforms: ${{ matrix.platform }}
133+
push: true
134+
tags: ${{ steps.meta.outputs.tags }}
135+
labels: ${{ steps.meta.outputs.labels }}
136+
137+
# Stitch the per-arch tags into a single multi-arch manifest under the bare PR tag.
138+
manifest:
139+
if: ${{ github.event.action != 'closed' }}
140+
name: Multi-arch manifest
141+
needs: [ resolve, build_arch ]
142+
runs-on: ubuntu-latest
143+
steps:
144+
- name: Log in to GHCR
145+
uses: docker/login-action@v4
146+
with:
147+
registry: ghcr.io
148+
username: ${{ github.actor }}
149+
password: ${{ secrets.GITHUB_TOKEN }}
150+
151+
- name: Set up Buildx
152+
uses: docker/setup-buildx-action@v4
153+
154+
- name: Create multi-arch manifest
155+
shell: bash
156+
run: |
157+
BASE_TAG="${{ needs.resolve.outputs.image }}:${{ needs.resolve.outputs.tag }}"
158+
AMD_TAG="${BASE_TAG}-amd64"
159+
ARM_TAG="${BASE_TAG}-arm64"
160+
161+
# If the arm64 build was skipped for any reason, only include amd64.
162+
if docker buildx imagetools inspect "$ARM_TAG" >/dev/null 2>&1; then
163+
docker buildx imagetools create -t "$BASE_TAG" "$AMD_TAG" "$ARM_TAG"
164+
else
165+
docker buildx imagetools create -t "$BASE_TAG" "$AMD_TAG"
166+
fi
167+
168+
- name: Summary
169+
if: ${{ always() }}
170+
shell: bash
171+
run: |
172+
echo "### GEMC PR image (multi-arch)" >> "$GITHUB_STEP_SUMMARY"
173+
echo '```' >> "$GITHUB_STEP_SUMMARY"
174+
echo "${{ needs.resolve.outputs.image }}:${{ needs.resolve.outputs.tag }}" >> "$GITHUB_STEP_SUMMARY"
175+
echo '```' >> "$GITHUB_STEP_SUMMARY"
176+
177+
cleanup:
178+
if: ${{ github.event.action == 'closed' }}
179+
name: Delete PR image
180+
runs-on: ubuntu-latest
181+
steps:
182+
- name: Checkout repository
183+
uses: actions/checkout@v6
184+
185+
- name: Delete pr-<number> images from GHCR
186+
env:
187+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
188+
OWNER: ${{ github.repository_owner }}
189+
PR: ${{ github.event.pull_request.number }}
190+
run: |
191+
source ci/tags_config.sh
192+
package="$(lc "${GITHUB_REPOSITORY##*/}")"
193+
# This workflow publishes the bare "…-pr-<number>" manifest plus the
194+
# "…-pr-<number>-amd64" / "…-pr-<number>-arm64" per-arch tags. Match all
195+
# three, anchored so PR 12 does not also match PR 123.
196+
pr_regex="-pr-${PR}(-amd64|-arm64)?\$"
197+
echo "Searching ${OWNER}/${package} for versions tagged /${pr_regex}/"
198+
199+
# gemc is an organisation, so use the orgs packages endpoint.
200+
ids="$(gh api --paginate \
201+
"/orgs/${OWNER}/packages/container/${package}/versions" \
202+
--jq "[.[] | select(any(.metadata.container.tags[]?; test(\"${pr_regex}\"))) | .id] | .[]" \
203+
2>/dev/null || true)"
204+
205+
if [[ -z "$ids" ]]; then
206+
echo "No matching package versions found; nothing to delete."
207+
exit 0
208+
fi
209+
210+
for id in $ids; do
211+
echo "Deleting version ${id}"
212+
gh api -X DELETE "/orgs/${OWNER}/packages/container/${package}/versions/${id}"
213+
done

.github/workflows/trigger_clas12_systems_tests.yml renamed to .github/workflows/trigger_c12s_tests.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
name: Trigger clas12-systems tests
22

3-
run-name: Triggered by successful deploy workflow
4-
53
# Fires a workflow_dispatch on gemc/clas12-systems' Test workflow whenever the
64
# gemc/src Deploy workflow succeeds on a push to main, i.e. after fresh
75
# ghcr.io/gemc/src images (which clas12-systems' tests run inside) are published.
@@ -44,4 +42,4 @@ jobs:
4442
-H "Authorization: Bearer ${{ secrets.CLAS12_SYSTEMS_PAT }}" \
4543
-H "X-GitHub-Api-Version: 2022-11-28" \
4644
https://api.github.com/repos/gemc/clas12-systems/actions/workflows/test.yml/dispatches \
47-
-d '{"ref":"main","inputs":{"triggered_by":"gemc/src deploy"}}'
45+
-d '{"ref":"main","inputs":{"triggered_by":"Triggered by gemc/src deploy"}}'

CONTRIBUTING.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,36 @@ Before requesting review, check that:
7575

7676
Reviews may ask for changes to improve correctness, maintainability, performance, documentation, or test coverage.
7777

78+
### Preview Container Image
79+
80+
When a pull request is opened, the [`pr-docker-image`](.github/workflows/pr-docker-image.yml) workflow builds
81+
a ready-to-run container image **from the code in that pull request** and rebuilds it on every push. Like the
82+
released images it is a multi-arch manifest covering `amd64` and `arm64`, built on the latest AlmaLinux base
83+
(currently AlmaLinux 10) and tagged with the pull request number:
84+
85+
```text
86+
ghcr.io/gemc/src:dev-almalinux-10-pr-<number>
87+
```
88+
89+
For example, pull request #123 publishes `ghcr.io/gemc/src:dev-almalinux-10-pr-123`, which anyone can pull and
90+
run exactly like the released images:
91+
92+
```shell
93+
docker run -it --rm ghcr.io/gemc/src:dev-almalinux-10-pr-123 bash
94+
```
95+
96+
Why this helps review:
97+
98+
- **Test the exact branch** — the pull request code is compiled and installed in a clean container, so authors
99+
and reviewers can validate behavior without building anything locally.
100+
- **Reproducible review** — reviewers and CI exercise the *same* artifact, removing "works on my machine"
101+
ambiguity.
102+
- **No local toolchain required** — testing only needs Docker, not a full Geant4/gemc build environment.
103+
- **Runs natively everywhere** — the multi-arch manifest serves `amd64` and `arm64`, so it runs natively on
104+
both Intel/AMD hosts and Apple-silicon/ARM machines.
105+
- **Self-cleaning** — when the pull request is closed or merged, the workflow automatically deletes the
106+
`…-pr-<number>` image, so stale preview tags do not accumulate.
107+
78108
## Communication
79109

80110
- General questions: open an issue.

README.md

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,40 +67,20 @@ Choose the installation path based on what you need:
6767
GEMC uses [Meson](https://mesonbuild.com). A normal source build is:
6868

6969
```shell
70-
git clone --recurse-submodules https://github.com/gemc/src.git gemc-src
70+
git clone https://github.com/gemc/src.git gemc-src
7171
cd gemc-src
7272
meson setup build --native-file=core.ini --prefix=/path/to/gemc
73-
meson compile -C build
7473
meson install -C build
7574
```
7675

77-
After installation, add the GEMC binary directory and the bundled Python environment to your shell:
76+
Then add the GEMC binary directory and the bundled Python environment to your shell:
7877

7978
```shell
8079
export GEMC_HOME=/path/to/gemc
8180
export PATH=$GEMC_HOME/bin:$GEMC_HOME/python_env/bin:$PATH
8281
```
8382

84-
For versioned installs such as `/path/to/gemc/0.2`, keep the version in the path:
85-
86-
```shell
87-
export GEMC_VERSION=0.2
88-
export PATH=/path/to/gemc/$GEMC_VERSION/bin:/path/to/gemc/$GEMC_VERSION/python_env/bin:$PATH
89-
```
90-
91-
Verify both the simulator and Python tools:
92-
93-
```shell
94-
gemc -v
95-
gemc-system-template --help
96-
gemc-analyzer --help
97-
```
98-
99-
Build options:
100-
101-
- `-Droot=enabled` enables ROOT output when ROOT is installed.
102-
- `-Di_test=true` enables GUI-oriented tests.
103-
- `meson install -C build` builds and installs the current tree.
83+
See the [installation page](https://gemc.github.io/home/installation/) for the Geant4 prerequisites, supported platforms, versioned-install paths, and build options (such as `-Droot=enabled` for ROOT output).
10484

10585
### Docker
10686

@@ -112,13 +92,13 @@ docker run -it --rm -v "$PWD":/work ghcr.io/gemc/src:dev-ubuntu-24.04 bash
11292

11393
Published image families include:
11494

115-
| OS | Version | Architectures |
116-
|------------|------------------------|-------------------|
117-
| AlmaLinux | 10 `amd64`, `arm64` |
118-
| Arch Linux | latest | `amd64` |
119-
| Debian | 13 | `amd64`, `arm64` |
120-
| Fedora | 44 | `amd64`, `arm64` |
121-
| Ubuntu | 24.04, 26.04 | `amd64`, `arm64` |
95+
| OS | Version | Architectures |
96+
|------------|--------------|-------------------|
97+
| AlmaLinux | 10 | `amd64`, `arm64` |
98+
| Arch Linux | latest | `amd64` |
99+
| Debian | 13 | `amd64`, `arm64` |
100+
| Fedora | 44 | `amd64`, `arm64` |
101+
| Ubuntu | 24.04, 26.04 | `amd64`, `arm64` |
122102

123103
See the [installation page](https://gemc.github.io/home/installation/) for Docker, browser GUI, and Apptainer examples.
124104

@@ -296,7 +276,7 @@ Run one test with logs:
296276
meson test -C build -v --print-errorlogs <testname>
297277
```
298278

299-
The CI system also builds Docker images, runs the Meson test suite, runs sanitizer builds, generates Doxygen documentation, performs CodeQL analysis, and publishes nightly development release notes.
279+
The CI system also builds Docker images, runs the Meson test suite, runs sanitizer builds, generates Doxygen documentation, performs CodeQL analysis, and publishes nightly development release notes. Each pull request additionally publishes a ready-to-run, multi-arch container image built from the branch, so authors and reviewers can test it without a local build — see [Preview container image](CONTRIBUTING.md#preview-container-image) in the contributing guide.
300280

301281
## Documentation
302282

ci/tags_config.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ OS_VERSIONS=(
3030
"archlinux=latest"
3131
)
3232

33+
get_latest_almalinux() {
34+
# Highest almalinux version listed in OS_VERSIONS (e.g. 9.4 vs 10 -> 10).
35+
local pair ver max=""
36+
for pair in "${OS_VERSIONS[@]}"; do
37+
[[ "${pair%%=*}" == "almalinux" ]] || continue
38+
ver="${pair#*=}"
39+
if [[ -z "$max" ]] || [[ "$(printf '%s\n%s\n' "$max" "$ver" | sort -V | tail -1)" == "$ver" ]]; then
40+
max="$ver"
41+
fi
42+
done
43+
printf '%s' "$max"
44+
}
45+
3346
build_image_ref() {
3447
# Owner from env (Actions sets this). Fallback for local runs.
3548
local owner="${GITHUB_REPOSITORY_OWNER:-gemc}"

gemc/goptions/goptions.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <iostream>
2424
#include <cstring>
2525
#include <cctype>
26+
#include <cstdlib>
2627

2728
using namespace std;
2829

@@ -617,6 +618,22 @@ void GOptions::print_version() {
617618
cout << " Called from: " << KGRN << executableCallingDir << RST << endl;
618619
cout << " Install: " << KGRN << installDir << "/bin" << RST << endl; //
619620
cout << " Released on: " << KGRN << grelease_date << RST << endl;
621+
622+
// Report the plugin search path when one was provided, either through the
623+
// -plugin_path option (or a plugin_path: YAML key) or the GEMC_PLUGIN_PATH
624+
// environment variable.
625+
string plugin_path = doesOptionExist("plugin_path") ? getScalarString("plugin_path") : "";
626+
if (plugin_path == "NULL") plugin_path = "";
627+
const char* plugin_env = std::getenv("GEMC_PLUGIN_PATH");
628+
if (!plugin_path.empty() || (plugin_env != nullptr && plugin_env[0] != '\0')) {
629+
string combined = plugin_path;
630+
if (plugin_env != nullptr && plugin_env[0] != '\0') {
631+
if (!combined.empty()) combined += ':';
632+
combined += plugin_env;
633+
}
634+
cout << " Plugin path: " << KGRN << combined << RST << endl;
635+
}
636+
620637
cout << " GEMC Reference: " << KGRN << greference << RST << endl;
621638
cout << " GEMC Homepage: " << KGRN << gweb << RST << endl;
622639
cout << " Author: " << KGRN << gauthor << RST << endl << endl;

0 commit comments

Comments
 (0)