Skip to content

Commit 4d5f08f

Browse files
committed
RHAIENG-2846 Prefetch: COMPONENT_DIR from Makefile, local build default, Tekton discovery
- GHA: Derive COMPONENT_DIR from Makefile; fail clearly if missing. - Makefile: Always pass LOCAL_BUILD=true for hermetic targets; require cachi2/output when prefetch-input exists; fix empty-variable warning. - prefetch-all.sh: Add find_tekton_yaml (ODH/RHDS by dockerfile param); remove --tekton-file and package-lock fallback; allow running without variant dir. - download-npm.sh: Exit 0 when Tekton file has no npm entries. - Regenerate artifacts.lock.yaml and pylock.cpu.toml; update README.
1 parent 3c7287e commit 4d5f08f

File tree

7 files changed

+146
-93
lines changed

7 files changed

+146
-93
lines changed

.github/workflows/build-notebooks-TEMPLATE.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,13 @@ jobs:
280280
id: prefetch
281281
run: |
282282
set -Eeuxo pipefail
283-
COMPONENT_DIR=$(echo "${{ inputs.target }}" | sed 's|-|/|')
283+
# Derive component dir from Makefile (single source of truth). Works for all
284+
# image targets (codeserver, jupyter-*, runtime-*, rstudio-*, base-images-*).
285+
COMPONENT_DIR=$(make -n "${{ inputs.target }}" 2>&1 | sed -n 's/.*#\*# Image build directory: <\([^>]*\)>.*/\1/p' | head -n1)
286+
if [ -z "$COMPONENT_DIR" ]; then
287+
echo "Could not derive COMPONENT_DIR from Makefile for target ${{ inputs.target }}" >&2
288+
exit 1
289+
fi
284290
if [ -d "$COMPONENT_DIR/prefetch-input" ]; then
285291
echo "Hermetic build detected — prefetching dependencies for $COMPONENT_DIR"
286292
pip3 install --quiet --break-system-packages pyyaml

Makefile

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ SHELL := bash
1111
.DELETE_ON_ERROR:
1212
MAKEFLAGS += --warn-undefined-variables
1313
MAKEFLAGS += --no-builtin-rules
14+
# Used where we need an empty expansion (avoids undefined variable warning).
15+
empty :=
1416

1517
# todo: leave the default recipe prefix for now
1618
ifeq ($(origin .RECIPEPREFIX), undefined)
@@ -85,14 +87,19 @@ define build_image
8587
awk -F= '!/^#/ && NF {gsub(/^[ \t]+|[ \t]+$$/, "", $$1); gsub(/^[ \t]+|[ \t]+$$/, "", $$2); printf "--build-arg %s=%s ", $$1, $$2}' $(CONF_FILE); \
8688
fi))
8789

88-
# Hermetic local build: when cachi2/output/ exists AND this target has a
89-
# prefetch-input/ directory, mount pre-downloaded deps and set LOCAL_BUILD=true.
90-
$(eval CACHI2_VOLUME := $(if $(and $(wildcard cachi2/output),$(wildcard $(BUILD_DIR)prefetch-input)),--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z --build-arg LOCAL_BUILD=true,))
90+
# Make is only used for local and GHA builds (Konflux does not run make).
91+
# Default to local build for hermetic-capable targets: always set LOCAL_BUILD=true
92+
# when prefetch-input/ exists; mount cachi2/output only when it exists (after prefetch).
93+
$(eval LOCAL_BUILD_ARG := $(if $(wildcard $(BUILD_DIR)prefetch-input),--build-arg LOCAL_BUILD=true,))
94+
$(eval CACHI2_VOLUME := $(if $(and $(wildcard cachi2/output),$(wildcard $(BUILD_DIR)prefetch-input)),--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z,))
95+
# Fail fast with a clear message if team runs make without prefetching first.
96+
# Skip when goal is all-images (gen_gha_matrix_jobs.py runs make for that to get the target list).
97+
$(if $(wildcard $(BUILD_DIR)prefetch-input),$(if $(wildcard cachi2/output),,$(if $(filter all-images,$(MAKECMDGOALS)),,$(error Prefetch required for hermetic build. Run: scripts/lockfile-generators/prefetch-all.sh --component-dir $(patsubst %/,%,$(BUILD_DIR)) — see scripts/lockfile-generators/README.md))),)
9198

9299
$(info # Building $(IMAGE_NAME) using $(DOCKERFILE_NAME) with $(CONF_FILE) and $(BUILD_ARGS)...)
93100

94101
$(ROOT_DIR)/scripts/sandbox.py --dockerfile '$(2)' --platform '$(BUILD_ARCH)' -- \
95-
$(CONTAINER_ENGINE) build $(CONTAINER_BUILD_CACHE_ARGS) $(CACHI2_VOLUME) --platform=$(BUILD_ARCH) --label release=$(RELEASE) --tag $(IMAGE_NAME) --file '$(2)' $(BUILD_ARGS) {}\;
102+
$(CONTAINER_ENGINE) build $(CONTAINER_BUILD_CACHE_ARGS) $(LOCAL_BUILD_ARG) $(CACHI2_VOLUME) --platform=$(BUILD_ARCH) --label release=$(RELEASE) --tag $(IMAGE_NAME) --file '$(2)' $(BUILD_ARGS) {}\;
96103
endef
97104

98105
# Push function for the notebook image:
@@ -111,8 +118,8 @@ endef
111118
define image
112119
$(eval BUILD_DIRECTORY := $(shell echo $(2) | sed 's/\/Dockerfile.*//'))
113120
$(eval VARIANT := $(shell echo $(notdir $(2)) | cut -d. -f2))
114-
$(eval DOCKERFILE := $(BUILD_DIRECTORY)/Dockerfile$(if $(KONFLUX:no=),.konflux,$()).$(VARIANT))
115-
$(eval CONF_FILE := $(BUILD_DIRECTORY)/build-args/$(if $(KONFLUX:no=),konflux.,$())$(shell echo $(VARIANT)).conf)
121+
$(eval DOCKERFILE := $(BUILD_DIRECTORY)/Dockerfile$(if $(KONFLUX:no=),.konflux,$(empty)).$(VARIANT))
122+
$(eval CONF_FILE := $(BUILD_DIRECTORY)/build-args/$(if $(KONFLUX:no=),konflux.,$(empty))$(shell echo $(VARIANT)).conf)
116123
$(info #*# Image build Dockerfile: <$(DOCKERFILE)> #(MACHINE-PARSED LINE)#*#...)
117124
$(info #*# Image build directory: <$(BUILD_DIRECTORY)> #(MACHINE-PARSED LINE)#*#...)
118125

codeserver/ubi9-python-3.12/prefetch-input/odh/artifacts.lock.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ artifacts:
3535
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-x86_64-unknown-linux-musl.tar.gz
3636
checksum: sha256:324cd645481db1ceda8621409c9151fc58d182b90cc5c428db80edb21fb26df3
3737
filename: ripgrep-v13.0.0-13-x86_64-unknown-linux-musl.tar.gz
38-
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-aarch64-unknown-linux-musl.tar.gz
39-
checksum: sha256:0f308620a428f56fe871fcc5d7c668c461dfed3244f717b698f3e9e92aca037a
38+
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-aarch64-unknown-linux-gnu.tar.gz
39+
checksum: sha256:1b0ca509f8707f2128f1b3ef245c3ea666d49a737431288536d49bd74652d143
4040
filename: ripgrep-v13.0.0-13-aarch64-unknown-linux-musl.tar.gz
4141
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-powerpc64le-unknown-linux-gnu.tar.gz
4242
checksum: sha256:a3fdb2c6ef9d4ff927ca1cb1e56f7aed7913d1be4dd4546aec400118c26452ab
43-
filename: ripgrep-v13.0.0-13-powerpc64le-unknown-linux-gnu.tar.gz
43+
filename: ripgrep-v13.0.0-13-powerpc64le-unknown-linux-musl.tar.gz
4444
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-13/ripgrep-v13.0.0-13-s390x-unknown-linux-gnu.tar.gz
4545
checksum: sha256:555983c74789d553b107c5a2de90b883727b3e43d680f0cd289a07407efb2755
46-
filename: ripgrep-v13.0.0-13-s390x-unknown-linux-gnu.tar.gz
46+
filename: ripgrep-v13.0.0-13-s390x-unknown-linux-musl.tar.gz
4747
- download_url: https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-4/ripgrep-v13.0.0-4-powerpc64le-unknown-linux-gnu.tar.gz
4848
checksum: sha256:3ddd7c0797c14cefd3ee61f13f15ac219bfecee8e6f6e27fd15c102ef229653a
4949
filename: ripgrep-v13.0.0-4-powerpc64le-unknown-linux-gnu.tar.gz

codeserver/ubi9-python-3.12/uv.lock.d/pylock.cpu.toml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/lockfile-generators/README.md

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,30 @@ gmake codeserver-ubi9-python-3.12 BUILD_ARCH=linux/arm64 PUSH_IMAGES=no
7575
| `--component-dir DIR` | Component directory (required), e.g. `codeserver/ubi9-python-3.12` |
7676
| `--rhds` | Use downstream (RHDS) lockfiles instead of upstream (ODH, the default) |
7777
| `--flavor NAME` | Lock file flavor (default: `cpu`) |
78-
| `--tekton-file FILE` | Tekton PipelineRun YAML for npm path discovery (auto-detected from `.tekton/` if omitted) |
7978
| `--activation-key KEY` | Red Hat activation key for RHEL RPMs (optional) |
8079
| `--org ORG` | Red Hat organization ID for RHEL RPMs (optional) |
8180

8281
### What it does
8382

8483
| Step | Condition | Script called |
8584
|------|-----------|---------------|
86-
| 1. Generic artifacts | `artifacts.in.yaml` exists | `create-artifact-lockfile.py` |
87-
| 2. Pip wheels | `pyproject.toml` exists | `create-requirements-lockfile.sh --download` |
88-
| 3. NPM packages | `package-lock.json` files found | `download-npm.sh` |
89-
| 4. RPMs | `rpms.in.yaml` exists | `hermeto-fetch-rpm.sh` (if lockfile committed) or `create-rpm-lockfile.sh --download` |
85+
| 1. Generic artifacts | `prefetch-input/<variant>/artifacts.in.yaml` exists | `create-artifact-lockfile.py` |
86+
| 2. Pip wheels | `pyproject.toml` exists in component dir | `create-requirements-lockfile.sh --download` |
87+
| 3. NPM packages | Tekton PipelineRun found for component (see below) | `download-npm.sh --tekton-file` |
88+
| 4. RPMs | `prefetch-input/<variant>/rpms.in.yaml` exists | `hermeto-fetch-rpm.sh` (if lockfile committed) or `create-rpm-lockfile.sh --download` |
89+
90+
**Variant directory:** Lockfiles live under `prefetch-input/odh/` (upstream) or
91+
`prefetch-input/rhds/` (downstream). If that directory is missing, steps 1 and 4
92+
are skipped; steps 2 (pip) and 3 (npm) still run when their inputs exist
93+
(`pyproject.toml`, or a Tekton file for the component).
94+
95+
**Step 3 (NPM):** The script finds the Tekton file automatically via
96+
`find_tekton_yaml`: it looks for a `.tekton/*pull-request*.yaml` whose
97+
`dockerfile` param matches this component — RHDS first
98+
(`COMPONENT_DIR/Dockerfile.konflux.*`), then ODH (`COMPONENT_DIR/Dockerfile.*`).
99+
If no Tekton file is found, npm is skipped. If the Tekton file has no
100+
`npm`-type `prefetch-input` entries, `download-npm.sh` exits successfully
101+
(nothing to download).
90102

91103
Steps are skipped if their input files don't exist. For RPMs, if
92104
`rpms.lock.yaml` is already committed, it downloads directly (skipping
@@ -95,11 +107,13 @@ lockfile regeneration) — this avoids cross-platform issues on arm64 CI runners
95107
### GitHub Actions integration
96108

97109
The GHA workflow template (`.github/workflows/build-notebooks-TEMPLATE.yaml`)
98-
calls `prefetch-all.sh` automatically for codeserver targets before running
99-
`make`. Non-codeserver targets skip the prefetch step entirely. After the
100-
build, container tests run (e.g. `tests/containers` with pytest); image
101-
metadata is read from both Docker `Config` and `ContainerConfig` so labels
102-
work when the daemon is Podman (see
110+
derives the component directory from the **Makefile** (dry-run of the build
111+
target, parsing `#*# Image build directory: <...>`), so it works for all image
112+
targets (codeserver, jupyter-*, runtime-*, rstudio-*, base-images-*). Prefetch
113+
runs when `COMPONENT_DIR/prefetch-input` exists; otherwise the step is skipped.
114+
After the build, container tests run (e.g. `tests/containers` with pytest);
115+
image metadata is read from both Docker `Config` and `ContainerConfig` so
116+
labels work when the daemon is Podman (see
103117
[tests/containers/docs/github-vs-local-image-metadata.md](../../tests/containers/docs/github-vs-local-image-metadata.md)).
104118

105119
**uv version:** The repo root `uv.toml` specifies the `uv` version (e.g.
@@ -516,7 +530,8 @@ collisions. Files that already exist are skipped.
516530
- `--lock-file <path>` — process a single `package-lock.json`.
517531
- `--tekton-file <path>` — parse a Tekton PipelineRun YAML to discover all
518532
`npm`-type `prefetch-input` paths, then process every `package-lock.json`
519-
found under them.
533+
found under them. If the file has **no** `npm`-type entries, the script
534+
exits 0 (nothing to download) instead of erroring.
520535

521536
Both flags can be combined. URLs that are already local (`file:///cachi2/...`)
522537
are automatically skipped.
@@ -712,34 +727,48 @@ python3 scripts/lockfile-generators/helpers/download-pip-packages.py \
712727
After running `prefetch-all.sh`, the **recommended** way to build is via make:
713728

714729
```bash
715-
# Makefile auto-detects cachi2/output/ and injects --volume + LOCAL_BUILD=true
730+
# Make sets LOCAL_BUILD=true for hermetic targets; mounts cachi2/output when it exists
716731
gmake codeserver-ubi9-python-3.12 BUILD_ARCH=linux/arm64 PUSH_IMAGES=no
717732
```
718733

719-
The Makefile evaluates each target independently: `CACHI2_VOLUME` is only set
720-
when both `cachi2/output/` exists AND the target directory has a
721-
`prefetch-input/` subdirectory. Non-hermetic targets are completely unaffected.
734+
The Makefile sets `LOCAL_BUILD=true` for any target that has `prefetch-input/`;
735+
it adds the cachi2 volume only when `cachi2/output/` exists (after prefetch).
736+
Non-hermetic targets are unaffected.
722737

723738
### Alternative: manual podman build
724739

725-
For developers who want to run `podman build` directly, the key flags are:
740+
Running `podman build` directly differs from `gmake` in these ways:
726741

727-
- `-v $(realpath ./cachi2):/cachi2:z` bind-mount the prefetched dependencies
728-
so the Dockerfile can install from them offline.
729-
- `--build-arg LOCAL_BUILD=true` signals the Dockerfile that this is a local
730-
build (configures dnf to use the local cachi2 RPM repo).
742+
| Aspect | `gmake codeserver-ubi9-python-3.12 BUILD_ARCH=... PUSH_IMAGES=no` | Manual `podman build ...` |
743+
|--------|-------------------------------------------------------------------|---------------------------|
744+
| **Build context** | Minimal (via `scripts/sandbox.py`: only files needed by the Dockerfile) | Full repo (`.`). |
745+
| **Volume** | `--volume $(ROOT_DIR)cachi2/output:/cachi2/output:Z` (mounts only `cachi2/output`) | Often `-v ./cachi2:/cachi2` (mounts whole dir); equivalent is `-v ./cachi2/output:/cachi2/output:z`. |
746+
| **Build args** | From `build-args/cpu.conf`: `INDEX_URL`, `BASE_IMAGE`, `PYLOCK_FLAVOR` | You must pass these (and `LOCAL_BUILD=true`) explicitly. |
747+
| **Tag** | `$(IMAGE_REGISTRY):codeserver-ubi9-python-3.12-$(RELEASE)_$(DATE)` | Whatever you pass with `-t`. |
748+
| **Label** | `--label release=$(RELEASE)` | Omitted unless you add it. |
749+
| **Cache** | Default `CONTAINER_BUILD_CACHE_ARGS ?= --no-cache` | Podman uses its default cache unless you pass `--no-cache`. |
750+
751+
To approximate the make build when running podman manually, use the same volume
752+
path as make and pass all build-args from `build-args/cpu.conf`:
753+
754+
- Bind-mount **only** `cachi2/output` at `/cachi2/output` (same as make).
755+
- Pass `LOCAL_BUILD=true` and the same `BASE_IMAGE`, `PYLOCK_FLAVOR`, and
756+
`INDEX_URL` as in `codeserver/ubi9-python-3.12/build-args/cpu.conf`.
731757

732758
```bash
759+
# Same volume path as Makefile; build-args from build-args/cpu.conf
733760
podman build \
734761
-f codeserver/ubi9-python-3.12/Dockerfile.cpu \
735-
--platform linux/amd64 \
762+
--platform linux/arm64 \
736763
-t code-server-test \
737764
--build-arg LOCAL_BUILD=true \
738765
--build-arg BASE_IMAGE=quay.io/opendatahub/odh-base-image-cpu-py312-c9s:latest \
739766
--build-arg PYLOCK_FLAVOR=cpu \
740-
-v "$(realpath ./cachi2):/cachi2:z" \
767+
--build-arg INDEX_URL=https://console.redhat.com/api/pypi/public-rhai/rhoai/3.4-EA1/cpu-ubi9/simple/ \
768+
-v "$(realpath ./cachi2/output):/cachi2/output:z" \
741769
.
742770
```
743771

744-
To build for a different architecture, change `--platform` and `ARCH`
745-
accordingly (e.g. `linux/arm64` / `aarch64`, `linux/ppc64le` / `ppc64le`).
772+
To build for a different architecture, change `--platform` (e.g. `linux/amd64`,
773+
`linux/arm64`, `linux/ppc64le`). The manual command uses the **full repo** as
774+
context; make uses a **sandboxed** context for reproducibility.

scripts/lockfile-generators/download-npm.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ if [[ -n "$TEKTON_FILE" ]]; then
197197
npm_paths=$(extract_npm_paths_from_tekton "$TEKTON_FILE")
198198

199199
if [[ -z "$npm_paths" ]]; then
200-
error_exit "No npm-type prefetch-input entries found in $TEKTON_FILE"
200+
echo "No npm-type prefetch-input entries in $TEKTON_FILE — nothing to download"
201+
exit 0
201202
fi
202203

203204
while IFS= read -r npm_path; do

0 commit comments

Comments
 (0)