Skip to content

Commit 5bdab7b

Browse files
committed
deploy to docker hub in series to address concurrency
1 parent 3010bbf commit 5bdab7b

5 files changed

Lines changed: 140 additions & 49 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,6 @@ jobs:
205205
matrix: ${{ fromJSON(needs.discover.outputs.matrix_manifest) }}
206206
env:
207207
TAG4: ${{ format('{0}-{1}-{2}', matrix.gemc_tag, matrix.image, matrix.image_tag) }}
208-
# Docker Hub mirror target. Steps are skipped automatically when the
209-
# DOCKERHUB_USERNAME secret is not configured (e.g. on forks).
210-
# Override the namespace with the DOCKERHUB_REPO repo variable.
211-
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
212-
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO || 'docker.io/maureeungaro/gemc' }}
213208
steps:
214209
- name: Checkout repository
215210
uses: actions/checkout@v6
@@ -223,14 +218,6 @@ jobs:
223218
username: ${{ github.actor }}
224219
password: ${{ secrets.GITHUB_TOKEN }}
225220

226-
- name: Log in to Docker Hub
227-
if: ${{ env.DOCKERHUB_USERNAME != '' }}
228-
uses: docker/login-action@v4
229-
with:
230-
registry: docker.io
231-
username: ${{ secrets.DOCKERHUB_USERNAME }}
232-
password: ${{ secrets.DOCKERHUB_TOKEN }}
233-
234221
- name: Set up Buildx
235222
uses: docker/setup-buildx-action@v4
236223

@@ -256,17 +243,6 @@ jobs:
256243
docker buildx imagetools create -t "$BASE_TAG" "$AMD_TAG"
257244
fi
258245
259-
- name: Mirror manifest to Docker Hub
260-
if: ${{ env.DOCKERHUB_USERNAME != '' }}
261-
shell: bash
262-
run: |
263-
# Copy the already-built multi-arch manifest from GHCR to Docker Hub
264-
# registry-to-registry (no pull, no rebuild).
265-
SRC="${{ needs.discover.outputs.image }}:${{ env.TAG4 }}"
266-
DST="${DOCKERHUB_REPO}:${{ env.TAG4 }}"
267-
echo "Mirroring $SRC -> $DST"
268-
docker buildx imagetools create -t "$DST" "$SRC"
269-
270246
- name: Download logs artifacts (all arches)
271247
if: ${{ always() }}
272248
uses: actions/download-artifact@v7
@@ -298,12 +274,85 @@ jobs:
298274
path: ${{ env.MANIFEST_SUMMARY_FILE }}
299275
if-no-files-found: warn
300276

301-
# Push the Docker Hub overview README once per deploy. Skipped automatically
302-
# when the DOCKERHUB_USERNAME secret is absent (e.g. on forks).
277+
# Mirror the GHCR multi-arch manifests to Docker Hub. This runs as a single
278+
# serial job (NOT inside the manifest matrix): all OS families target the same
279+
# Docker Hub repo, and concurrent blob uploads to one repo trip Docker Hub's
280+
# rate limits, so tags are copied one at a time with retries. The copy is
281+
# registry-to-registry (no pull, no rebuild). Skipped when the
282+
# DOCKERHUB_USERNAME secret is absent (e.g. on forks).
283+
dockerhub_mirror:
284+
name: Mirror images to Docker Hub
285+
needs: [ discover, manifest ]
286+
if: ${{ always() && needs.manifest.result == 'success' }}
287+
runs-on: ubuntu-latest
288+
env:
289+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
290+
DOCKERHUB_REPO: ${{ vars.DOCKERHUB_REPO || 'docker.io/maureeungaro/gemc' }}
291+
steps:
292+
- name: Checkout repository
293+
uses: actions/checkout@v6
294+
with:
295+
ref: ${{ github.event.workflow_run.head_sha }}
296+
297+
- name: Log in to GHCR
298+
if: ${{ env.DOCKERHUB_USERNAME != '' }}
299+
uses: docker/login-action@v4
300+
with:
301+
registry: ghcr.io
302+
username: ${{ github.actor }}
303+
password: ${{ secrets.GITHUB_TOKEN }}
304+
305+
- name: Log in to Docker Hub
306+
if: ${{ env.DOCKERHUB_USERNAME != '' }}
307+
uses: docker/login-action@v4
308+
with:
309+
registry: docker.io
310+
username: ${{ secrets.DOCKERHUB_USERNAME }}
311+
password: ${{ secrets.DOCKERHUB_TOKEN }}
312+
313+
- name: Set up Buildx
314+
if: ${{ env.DOCKERHUB_USERNAME != '' }}
315+
uses: docker/setup-buildx-action@v4
316+
317+
- name: Mirror all tags (sequential, with retry)
318+
if: ${{ env.DOCKERHUB_USERNAME != '' }}
319+
shell: bash
320+
env:
321+
SRC_IMAGE: ${{ needs.discover.outputs.image }}
322+
run: |
323+
set -uo pipefail
324+
source ci/tags_config.sh
325+
326+
copy_with_retry() {
327+
local src=$1 dst=$2 n=0 max=5
328+
until docker buildx imagetools create -t "$dst" "$src"; do
329+
n=$((n + 1))
330+
if [ "$n" -ge "$max" ]; then
331+
echo "::error::failed to mirror $src -> $dst after $max attempts"
332+
return 1
333+
fi
334+
echo "retry $n/$max for $dst; backing off $((n * 15))s"
335+
sleep $((n * 15))
336+
done
337+
echo "mirrored $src -> $dst"
338+
}
339+
340+
rc=0
341+
for gemcv in $(get_gemc_tags); do
342+
for pair in "${OS_VERSIONS[@]}"; do
343+
os="${pair%%=*}"; ver="${pair#*=}"
344+
tag="${gemcv}-${os}-${ver}"
345+
copy_with_retry "${SRC_IMAGE}:${tag}" "${DOCKERHUB_REPO}:${tag}" || rc=1
346+
done
347+
done
348+
exit "$rc"
349+
350+
# Push the Docker Hub overview README once per deploy, after the images exist.
351+
# Skipped automatically when the DOCKERHUB_USERNAME secret is absent.
303352
dockerhub_readme:
304353
name: Update Docker Hub description
305-
needs: manifest
306-
if: ${{ always() && needs.manifest.result == 'success' }}
354+
needs: dockerhub_mirror
355+
if: ${{ always() && needs.dockerhub_mirror.result == 'success' }}
307356
runs-on: ubuntu-latest
308357
env:
309358
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -337,7 +386,7 @@ jobs:
337386
github.event.workflow_run.conclusion == 'success' &&
338387
github.event.workflow_run.event == 'push'
339388
}}
340-
needs: [ build_arch, manifest ]
389+
needs: [ build_arch, manifest, dockerhub_mirror, dockerhub_readme ]
341390
runs-on: ubuntu-latest
342391
steps:
343392
- name: Fail if any required job failed

gemc/gsystem/gsystem_options.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ GOptions defineOptions() {
9292
{"name", goptions::NODFLT, "system name (mandatory). For ascii factories, it may include the path to the file"},
9393
{"factory", GSYSTEMSQLITETFACTORYLABEL, "factory name."},
9494
{"variation", "default", "geometry variation"},
95-
{"annotations", UNINITIALIZEDSTRINGQUANTITY, "optional system annotations. Examples: \"mats_only\" "}
95+
{"annotations", UNINITIALIZEDSTRINGQUANTITY, "optional system annotations. Examples: \"mats_only\" "},
96+
{"digitization", UNINITIALIZEDSTRINGQUANTITY, "optional digitization plugin name when it differs from the system name (shared plugin, e.g. \"ecal\" for the EC and PCAL systems)"}
9697
};
9798
goptions.defineOption(GSYSTEM_LOGGER, "defines the group of volumes in a system", gsystem, help);
9899

gemc_options.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,18 @@ namespace gemc {
190190
if (!entry["name"]) continue;
191191
std::string name;
192192
try { name = entry["name"].as<std::string>(); } catch (...) { continue; }
193-
if (name.empty() || !seen_names.insert(name).second) continue;
194-
probe_plugin(name + ".gplugin");
193+
if (name.empty()) continue;
194+
if (seen_names.insert(name).second) probe_plugin(name + ".gplugin");
195+
196+
// A gsystem may be digitized by a plugin whose name differs from the system
197+
// name (e.g. the EC and PCAL systems share the "ecal" plugin). Probe that
198+
// plugin too so its options and verbosity domain are registered before the
199+
// factory is instantiated.
200+
if (entry["digitization"]) {
201+
std::string dig;
202+
try { dig = entry["digitization"].as<std::string>(); } catch (...) { dig.clear(); }
203+
if (!dig.empty() && seen_names.insert(dig).second) probe_plugin(dig + ".gplugin");
204+
}
195205
}
196206
}
197207

meson/g4_pkgconfig.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
- geant4_core.pc : libs with graphical/GUI/vis/X11/OpenGL/Qt removed
88
99
Usage:
10-
./g4_pkgconfig.py <install-prefix>
10+
./g4_pkgconfig.py <install-prefix> [geant4-config]
1111
1212
Example:
1313
./g4_pkgconfig.py $GEMC
14+
./g4_pkgconfig.py $GEMC $G4INSTALL/bin/geant4-config
1415
"""
1516
import subprocess
1617
import sys
@@ -21,7 +22,7 @@
2122
print(f"[debug] Python executable: {sys.executable}")
2223

2324

24-
# ────────────────────────── helpers ──────────────────────────
25+
# Helpers
2526
def run_config(command: str, option: str) -> str:
2627
try:
2728
result = subprocess.run([command, option], capture_output=True, text=True, check=True)
@@ -172,22 +173,22 @@ def generate_pkgconfig(install_prefix: Path,
172173
print(f"Generated {pc_path}")
173174

174175

175-
# ────────────────────────── main ──────────────────────────
176+
# Main
176177
if __name__ == "__main__":
177-
if len(sys.argv) != 2:
178-
sys.exit(f"Usage: {sys.argv[0]} <install-prefix>")
178+
if len(sys.argv) not in (2, 3):
179+
sys.exit(f"Usage: {sys.argv[0]} <install-prefix> [geant4-config]")
179180

180181
install_dir = Path(sys.argv[1]).expanduser().resolve()
182+
config_cmd = sys.argv[2] if len(sys.argv) == 3 else "geant4-config"
181183
if not install_dir.exists():
182184
print(f"Creating installation directory {install_dir}")
183185
install_dir.mkdir(parents=True, exist_ok=True)
184186

185-
generate_pkgconfig(install_dir, "geant4-config",
187+
generate_pkgconfig(install_dir, config_cmd,
186188
"geant4.pc", "Geant4", "Geant4 Simulation Toolkit")
187189

188-
generate_pkgconfig(install_dir, "geant4-config",
190+
generate_pkgconfig(install_dir, config_cmd,
189191
"geant4_core.pc", "Geant4 Core",
190192
"Geant4 Simulation Toolkit (core, no graphical/GUI libs)",
191193
cflags_filter=filter_graphical_cflags,
192194
libs_filter=filter_graphical_libs)
193-

meson/meson.build

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ expat_dep = dependency('expat', required: true)
3636
zlib_dep = dependency('zlib', static : false, required : true) # Use shared libz.so instead of libz.az
3737

3838
# pkg-config --cflags --libs clhep
39-
clhep_deps = dependency('clhep', version : '>=2.4.7.1', include_type : 'system') # treat as system include paths - no warnings
39+
clhep_deps = dependency(
40+
'clhep',
41+
version : '>=2.4.7.1',
42+
include_type : 'system',
43+
)
4044

4145
# feature: feature toggle (uncomment options() above)
4246
root_opt = get_option('root')
@@ -89,19 +93,44 @@ if (not root_dep.found()) and (not root_opt.disabled())
8993
endif
9094
endif
9195

92-
# geant4.pc if not found
96+
# Generate Geant4 pkg-config files from the active module so downstream builds
97+
# do not pick stale global pc files from unrelated pkg-config directories.
9398
fs = import('fs')
9499
gemc_prefix = get_option('prefix')
95100
geant4_pc_path = gemc_prefix / 'lib/pkgconfig'
96-
geant4_core_pc = geant4_pc_path / 'geant4_core.pc'
97-
geant4_pc = geant4_pc_path / 'geant4.pc'
98101
g4_pkgconfig_script = meson.project_source_root() / 'meson/g4_pkgconfig.py'
99-
if not fs.exists(geant4_pc) or not fs.exists(geant4_core_pc)
100-
message('geant4.pc or geant4_core.pc not found in ' + geant4_pc_path + ', running install script to install it in ' + gemc_prefix)
101-
run_command(g4_pkgconfig_script, gemc_prefix, check : true)
102+
103+
g4install = run_command('sh', '-c', 'printf %s "$G4INSTALL"', check : false).stdout().strip()
104+
geant4_config = find_program('geant4-config', required : false)
105+
if g4install != ''
106+
geant4_module_config = find_program(g4install / 'bin' / 'geant4-config', required : false)
107+
if geant4_module_config.found()
108+
geant4_config = geant4_module_config
109+
endif
110+
endif
111+
112+
if not geant4_config.found()
113+
error('geant4-config was not found. Load a Geant4 module before configuring GEMC.')
102114
endif
103-
geant4_dep = dependency('geant4', version : '>=11.3.2', method : 'pkg-config', include_type : 'system') # treat as system include paths - no warnings
115+
116+
geant4_config_version = run_command(geant4_config, '--version', check : true).stdout().strip()
117+
message('Generating Geant4 pkg-config files in ' + geant4_pc_path + ' from ' + geant4_config.full_path())
118+
run_command(g4_pkgconfig_script, gemc_prefix, geant4_config.full_path(), check : true)
119+
120+
geant4_dep = dependency(
121+
'geant4',
122+
version : '>=11.3.2',
123+
method : 'pkg-config',
124+
include_type : 'system',
125+
)
104126
geant4_core_dep = dependency('geant4_core', version : '>=11.3.2', method : 'pkg-config')
127+
if geant4_core_dep.version() != geant4_config_version
128+
error(
129+
'pkg-config resolved geant4_core ' + geant4_core_dep.version() +
130+
', but active geant4-config reports ' + geant4_config_version +
131+
'. Put ' + geant4_pc_path + ' before stale pkg-config directories and reconfigure.',
132+
)
133+
endif
105134
geant4_deps = [geant4_dep]
106135
geant4_core_deps = [geant4_core_dep]
107136
if sys == 'linux'
@@ -135,7 +164,8 @@ cmake_opts.add_cmake_defines({
135164
'BUILD_STATIC_LIBS' : true,
136165
'ZLIB_USE_STATIC_LIBS' : false, # prefer shared zlib
137166
'CMAKE_POSITION_INDEPENDENT_CODE' : true,
138-
'CMAKE_C_STANDARD' : 17, # avoid C23 warnings in bundled zlib if you ever enable it
167+
# Avoid C23 warnings in bundled zlib if you ever enable it.
168+
'CMAKE_C_STANDARD' : 17,
139169
'CMAKE_POLICY_VERSION_MINIMUM' : '4.0',
140170
})
141171

0 commit comments

Comments
 (0)