Skip to content

ART-22058: Discover transitive build deps missed by pip_find_builddeps.py - #83

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
mytreya-rh:fix-hermetic-build-calver
Aug 14, 2026
Merged

ART-22058: Discover transitive build deps missed by pip_find_builddeps.py#83
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
mytreya-rh:fix-hermetic-build-calver

Conversation

@mytreya-rh

@mytreya-rh mytreya-rh commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

The ART hermetic build for openshift-enterprise-ansible-operator has been failing since Aug 10 (9 consecutive build_error runs) with:

Collecting setuptools
  Using cached setuptools-82.0.1-py3-none-any.whl
ERROR: Could not find a version that satisfies the requirement calver

Root cause: trove-classifiers (pulled into requirements-build.txt only as a transitive dependency of hatchling) declares calver in its own pyproject.toml [build-system].requires. pip_find_builddeps.py never scans it — Stage 3 of generate_requirements.py only runs on Pipfile.lock's runtime packages, not on second-order build tools pulled in by pip-compile. Even pointed at it directly, pip_find_builddeps.py couldn't discover this, since it relies on pip's own dependency-report machinery, which doesn't surface [build-system] requirements for a package that already has a wheel (pypa/pip#7863). Hermeto/Cachi2 defaults to source-only prefetching, so every package — wheel or not — is actually built from its sdist inside the hermetic sandbox, invoking that requirement regardless.

Fix: Add Stage 4b to generate_requirements.py. After phase-splitting, it iteratively fetches each newly-resolved (previously unscanned) package's pyproject.toml directly from its sdist on PyPI — bypassing pip entirely — and injects any undeclared build-system requirement into the owning phase, converging to a fixed point. This is fully dynamic (no package names hardcoded), so it will catch the same class of gap for any future package, not just calver/trove-classifiers.

  • openshift/hack/generate_requirements.py: new _pyproject_build_system_requires() helper and stage4b_close_transitive_build_deps() stage, wired in after Stage 4.
  • openshift/hack/generate_requirements.md: new "Stage 4b" section documenting the problem and fix.
  • openshift/Dockerfile.requirements: updated stage comment list.
  • openshift/requirements-build.txt: regenerated — adds calver==2025.10.20 (the fix) and poetry-core (needed to build hatchling's new tomlkit dependency from source); other changes are routine pip-compile version drift (hatchling/wheel/vcs-versioning bumps) unrelated to this fix.

Test plan

  • Unit-tested _pyproject_build_system_requires directly against trove-classifiers (returns ['setuptools', 'calver']) and edge cases (missing package, no [build-system] table).
  • Isolated integration test with a real pip-tools venv reproduced the bug (hatchling → trove-classifiers with no calver) and confirmed Stage 4b's loop discovers and injects it correctly.
  • Full end-to-end make -f openshift/Makefile generate-requirements run (fresh --no-cache container build): Stage 4b fires twice, adds calver/poetry-core, converges cleanly; Stage 5/6 pass with no warnings.
  • Diff against committed files is minimal: only requirements-build.txt changed (the new packages plus expected drift); requirements.txt, requirements-pre-build.txt, requirements-build1.txt, Pipfile.lock untouched.
  • go build ./... and go vet ./... pass (no Go code touched).

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Build requirements generation now discovers transitive build dependencies automatically.
    • Requirements are iteratively updated until the dependency set is complete, with safeguards for convergence and recoverable failures.
  • Documentation

    • Added guidance describing the expanded generation workflow, performance considerations, and dependency discovery behavior.
  • Chores

    • Updated build tooling dependencies and versions to support the enhanced requirements-generation process.

…builddeps.py

The ART hermetic build for openshift-enterprise-ansible-operator has been
failing since Aug 10 (9 consecutive build_error runs) with:

  Collecting setuptools
    Using cached setuptools-82.0.1-py3-none-any.whl
  ERROR: Could not find a version that satisfies the requirement calver

trove-classifiers (pulled into requirements-build.txt only as a transitive
dependency of hatchling) declares `calver` in its own pyproject.toml
[build-system].requires. pip_find_builddeps.py never scans it, because
Stage 3 only runs on Pipfile.lock's runtime packages -- and even pointed at
it directly, pip_find_builddeps.py couldn't discover it anyway, since it
relies on pip's dependency-report machinery, which doesn't surface
build-system requirements for a package that already has a wheel
(pypa/pip#7863). Hermeto/Cachi2 defaults to source-only prefetching, so
every package -- wheel or not -- is actually built from its sdist in the
hermetic sandbox, invoking that requirement regardless.

Add Stage 4b: after phase-splitting, iteratively fetch each newly-resolved
package's pyproject.toml directly from its sdist (bypassing pip entirely)
and inject any undeclared build-system requirement into the owning phase,
converging to a fixed point. This is fully dynamic -- no package names are
hardcoded -- so it will catch the same class of gap for any future package,
not just this one.

Verified with a fresh `make generate-requirements` run: requirements-build.txt
now pins calver==2025.10.20 (and poetry-core, needed to build hatchling's new
tomlkit dependency from source); no other files changed beyond routine
version drift (hatchling/wheel/vcs-versioning bumps already expected from
re-running pip-compile).

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Walkthrough

The requirements generator now discovers transitive build requirements from package sdists, injects them into build phases, and iterates until closure or five passes. Supporting dependencies and documentation were updated.

Changes

Build dependency closure

Layer / File(s) Summary
Build dependency inputs
openshift/requirements-build.txt
Added calver, poetry-core, and tomlkit. Updated hatchling, vcs-versioning, and wheel.
Sdist build metadata discovery
openshift/hack/generate_requirements.py, openshift/hack/generate_requirements.md
Added _pyproject_build_system_requires, which retrieves sdists from PyPI and reads [build-system].requires from pyproject.toml.
Iterative closure integration
openshift/hack/generate_requirements.py, openshift/hack/generate_requirements.md, openshift/Dockerfile.requirements
Added Stage 4b to inject discovered requirements, recompile affected phases, revert failed injections, and run before CVE checks. Documented the stage, performance behavior, and pipeline position.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant main
  participant Stage4b
  participant _pyproject_build_system_requires
  participant PyPI
  participant build_phases
  main->>Stage4b: process compiled build-phase pins
  Stage4b->>_pyproject_build_system_requires: inspect uncovered package
  _pyproject_build_system_requires->>PyPI: fetch sdist metadata
  PyPI-->>_pyproject_build_system_requires: return build requirements
  _pyproject_build_system_requires-->>Stage4b: return discovered requirements
  Stage4b->>build_phases: inject requirements and recompile
  build_phases-->>Stage4b: return compiled phase results
  Stage4b-->>main: finish closure before CVE checks
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error Stage 4b prints fetched build-system requirements verbatim; a PEP 508 direct URL can contain credentials, tokens, email addresses, or internal hostnames. Log only the normalized package name, and redact credentials and sensitive URL components from requirement strings and pip error output before writing logs.
✅ Passed checks (14 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed The PR changes only requirements tooling, documentation, and a requirements file; the diff contains no Go test files or Ginkgo DSL calls, so no test name can violate this check.
Test Structure And Quality ✅ Passed The PR changes only Python, Markdown, Dockerfile, and requirements files; no Ginkgo test code or cluster operations changed, so the check is not applicable.
Microshift Test Compatibility ✅ Passed The commit changes only requirements-generation code, documentation, and a requirements file; it adds no Ginkgo e2e tests or MicroShift API references.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The PR changes only Python, Markdown, Dockerfile, and requirements files; it adds no Ginkgo e2e tests or node-topology assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed The commit changes only requirements generation, documentation, a Dockerfile comment, and build dependencies; it adds no deployment, operator, controller, or scheduling constraints.
Ote Binary Stdout Contract ✅ Passed The commit changes only Dockerfile, Markdown, Python, and requirements files; it adds no Go or OTE binary process-level code.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests were added. The diff only changes documentation, a Dockerfile, the Python requirements generator, and build requirements, so this check is inapplicable.
No-Weak-Crypto ✅ Passed The PR diff adds dependency-resolution, archive, and TOML parsing only; AST and keyword checks found no MD5, SHA1, DES, RC4, Blowfish, ECB, or secret-comparison code.
Container-Privileges ✅ Passed The patch adds only Dockerfile comments plus Python, documentation, and requirement changes; it introduces no privileged, host namespace, SYS_ADMIN, escalation, or root configuration.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: discovering transitive build dependencies missed by the existing dependency scanner.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@openshift-ci
openshift-ci Bot requested review from anik120 and grokspawn August 12, 2026 14:45
@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openshift/hack/generate_requirements.md`:
- Around line 441-445: Add the text language identifier to both fenced code
blocks in the requirements documentation: the error-output block near the
setuptools/calver message and the pseudocode block near the second reported
section. Leave their contents unchanged.

In `@openshift/hack/generate_requirements.py`:
- Around line 1171-1173: Update the Stage 3 probe tracking near rpm_norms and
probed so entries are keyed by phase and resolved package version rather than
normalized package name alone. Initialize probed from scanned using the matching
resolved versions, and ensure later probe checks preserve distinct package
versions across phases.
- Around line 1194-1228: Update the additions handling around _pip_compile so a
conflict in one build-system requirement does not discard compatible additions;
compile additions independently or split failed batches and retain successful
ones. Move probed.add(norm) from the initial loop until each package’s addition
is retained or explicitly rejected, ensuring failed batch entries remain
eligible for retry.
- Around line 1200-1207: Update the requirement handling loop around _norm and
resolved so req is parsed as a complete requirement, including markers and
specifiers, rather than comparing only its package name. Evaluate the marker and
check whether the resolved version satisfies the requirement; skip only
satisfied requirements, and append the original constraint when it is
unsatisfied while preserving the queued_norms behavior.
- Around line 233-235: Validate sdist_url at the trust boundary immediately
before urlopen in the sdist download flow. Allow only HTTPS URLs to the expected
PyPI file host, with no credentials and the default HTTPS port; reject every
other scheme, host, port, or malformed URL. Ensure redirects are disabled or
validated at each hop so the final download cannot leave the approved host.

In `@openshift/requirements-build.txt`:
- Around line 7-8: Update openshift/hack/generate_requirements.py to generate
build requirement files with hashes by enabling its --generate-hashes option,
and update openshift/install-ansible.sh to pass --require-hashes when installing
those requirements. Ensure regenerated artifacts contain hashes and pip enforces
them.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3e102a6e-dfdb-4274-a9ca-ed7e10570770

📥 Commits

Reviewing files that changed from the base of the PR and between de49db3 and 34d66d7.

📒 Files selected for processing (4)
  • openshift/Dockerfile.requirements
  • openshift/hack/generate_requirements.md
  • openshift/hack/generate_requirements.py
  • openshift/requirements-build.txt

Comment on lines +441 to +445
```
Collecting setuptools
Using cached setuptools-82.0.1-py3-none-any.whl
ERROR: Could not find a version that satisfies the requirement calver (from versions: none)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language identifiers to both fenced blocks.

Lines 441 and 459 start fenced blocks without a language identifier. markdownlint reports MD040 for both blocks. Use text for the error output and the pseudocode block.

Also applies to: 459-471

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 441-441: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/hack/generate_requirements.md` around lines 441 - 445, Add the text
language identifier to both fenced code blocks in the requirements
documentation: the error-output block near the setuptools/calver message and the
pseudocode block near the second reported section. Leave their contents
unchanged.

Source: Linters/SAST tools

Comment on lines +233 to +235
try:
with urllib.request.urlopen(sdist_url, timeout=60) as resp:
raw = resp.read()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Allow-list the sdist download origin.

sdist_url comes from remote JSON and reaches urlopen() without validation. A substituted metadata response can direct the generator to an internal, non-HTTPS, or otherwise unapproved endpoint.

Require HTTPS and the expected PyPI file host before the download. Reject credentials, non-default ports, redirects to other hosts, and all other schemes.

As per path instructions, “Validate at trust boundaries with allow-lists, not deny-lists.”

🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 233-233: Request-controlled URL passed to urlopen; validate against an allowlist to prevent SSRF.
Context: urllib.request.urlopen(sdist_url, timeout=60)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(urlopen-unsanitized-data)

🪛 Ruff (0.16.1)

[error] 234-234: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/hack/generate_requirements.py` around lines 233 - 235, Validate
sdist_url at the trust boundary immediately before urlopen in the sdist download
flow. Allow only HTTPS URLs to the expected PyPI file host, with no credentials
and the default HTTPS port; reject every other scheme, host, port, or malformed
URL. Ensure redirects are disabled or validated at each hop so the final
download cannot leave the approved host.

Sources: Path instructions, Linters/SAST tools

Comment on lines +1171 to +1173
rpm_norms = {_norm(p) for p in RPM_INSTALLED}
probed: set[str] = set(scanned)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Track probes by phase and resolved version.

probed stores only normalized package names. _pyproject_build_system_requires() reads version-specific metadata, and Stage 4 can place different versions in different phases. After probing package==A, this code skips package==B in another phase.

Key probe state by phase and version. Initialize the Stage 3 probe state with the matching resolved versions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/hack/generate_requirements.py` around lines 1171 - 1173, Update the
Stage 3 probe tracking near rpm_norms and probed so entries are keyed by phase
and resolved package version rather than normalized package name alone.
Initialize probed from scanned using the matching resolved versions, and ensure
later probe checks preserve distinct package versions across phases.

Comment on lines +1194 to +1228
additions: list[str] = []
queued_norms: set[str] = set()
for norm, pkg_line in sorted(new_pkgs.items()):
probed.add(norm)
pkg_name, pkg_version = pkg_line.split("==", 1)
requires = _pyproject_build_system_requires(pkg_name, pkg_version)
for req in requires:
m = re.match(r"^([A-Za-z0-9][A-Za-z0-9._-]*)", req.strip())
if not m:
continue
req_norm = _norm(m.group(1))
if req_norm in resolved or req_norm in queued_norms:
continue # already pinned in this phase, or already queued below
additions.append(req.strip())
queued_norms.add(req_norm)
print(
f" + {req.strip()} (build-system requirement of"
f" {pkg_name}=={pkg_version}, undetected by"
" pip_find_builddeps.py — see pypa/pip#7863)"
)

if not additions:
continue

original_in = in_path.read_text()
in_path.write_text(original_in.rstrip("\n") + "\n" + "\n".join(additions) + "\n")
ok, stderr = _pip_compile(in_path, txt_path, ["--allow-unsafe"])
if not ok:
print(
f" WARNING: pip-compile failed after injecting"
f" {additions} into {label}; reverting:\n{stderr}",
file=sys.stderr,
)
in_path.write_text(original_in)
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep compatible additions when one addition conflicts.

This code compiles all additions as one batch. If one requirement conflicts, Lines 1221-1228 revert every addition. Lines 1196-1199 already mark every package as probed, so compatible requirements dropped with the batch are never retried.

Compile additions independently, or split a failed batch. Record probe completion only after an addition is retained or explicitly recorded as rejected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/hack/generate_requirements.py` around lines 1194 - 1228, Update the
additions handling around _pip_compile so a conflict in one build-system
requirement does not discard compatible additions; compile additions
independently or split failed batches and retain successful ones. Move
probed.add(norm) from the initial loop until each package’s addition is retained
or explicitly rejected, ensuring failed batch entries remain eligible for retry.

Comment on lines +1200 to +1207
for req in requires:
m = re.match(r"^([A-Za-z0-9][A-Za-z0-9._-]*)", req.strip())
if not m:
continue
req_norm = _norm(m.group(1))
if req_norm in resolved or req_norm in queued_norms:
continue # already pinned in this phase, or already queued below
additions.append(req.strip())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check whether the resolved version satisfies the requirement.

The req_norm in resolved check only compares package names. If the phase contains calver==2024.1 and the sdist requires calver>=2025, this code skips the constraint and leaves an incompatible build environment.

Parse the complete requirement, evaluate its marker and specifier against the resolved version, and inject the constraint when it is not satisfied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/hack/generate_requirements.py` around lines 1200 - 1207, Update the
requirement handling loop around _norm and resolved so req is parsed as a
complete requirement, including markers and specifiers, rather than comparing
only its package name. Evaluate the marker and check whether the resolved
version satisfies the requirement; skip only satisfied requirements, and append
the original constraint when it is unsatisfied while preserving the queued_norms
behavior.

Comment on lines +7 to +8
calver==2025.10.20
# via -r /requirements-build.in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --glob='*.py' --glob='*.sh' --glob='Dockerfile*' \
  'pip-compile|--generate-hashes|--require-hashes|pip install.*requirements-(pre-build|build1|build)\.txt' \
  openshift

Repository: openshift/ansible-operator-plugins

Length of output: 4840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- requirements-build.txt ---'
cat -n openshift/requirements-build.txt | sed -n '1,90p'

printf '%s\n' '--- install-ansible.sh ---'
cat -n openshift/install-ansible.sh | sed -n '1,45p'

printf '%s\n' '--- generation and container configuration ---'
cat -n openshift/Dockerfile.requirements | sed -n '1,90p'
printf '%s\n' '--- requirements files ---'
for f in openshift/requirements-*.txt; do
  printf '\n[%s]\n' "$f"
  sed -n '1,14p' "$f"
done

printf '%s\n' '--- repository diff summary and changed dependency lines ---'
git diff --stat
git diff -- openshift/requirements-build.txt openshift/install-ansible.sh openshift/Dockerfile.requirements | sed -n '1,220p'

printf '%s\n' '--- hash-related configuration and consumers ---'
rg -n --hidden --glob '!*.pyc' \
  'generate-hashes|require-hashes|--hash=|requirements-(pre-build|build1|build)\.txt|PIP_OPTS' \
  openshift

Repository: openshift/ansible-operator-plugins

Length of output: 13196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- pip-compile invocations and output writing ---'
rg -n -C 8 \
  'def .*compile|pip-compile|generate-hashes|--output-file|requirements-(pre-build|build1|build)\.txt|write_text|write_bytes' \
  openshift/hack/generate_requirements.py

printf '%s\n' '--- all compiled requirement contents and hash presence ---'
for f in openshift/requirements.txt openshift/requirements-pre-build.txt openshift/requirements-build1.txt openshift/requirements-build.txt; do
  if [ -f "$f" ]; then
    printf '\n[%s]\n' "$f"
    cat -n "$f" | sed -n '1,45p'
  fi
done

printf '%s\n' '--- requirement inputs and generation documentation ---'
for f in openshift/*.in; do
  printf '\n[%s]\n' "$f"
  cat -n "$f"
done
rg -n -C 5 'hash|pip-compile|requirements-build\.txt|install-ansible' \
  openshift/hack/generate_requirements.md openshift/Makefile

Repository: openshift/ansible-operator-plugins

Length of output: 37667


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- final build-file generation and post-processing ---'
sed -n '995,1140p' openshift/hack/generate_requirements.py
sed -n '1180,1240p' openshift/hack/generate_requirements.py
sed -n '1340,1405p' openshift/hack/generate_requirements.py

printf '%s\n' '--- deterministic hash coverage check ---'
python3 - <<'PY'
from pathlib import Path
import re

for path in sorted(Path("openshift").glob("requirements*.txt")):
    text = path.read_text()
    active = []
    current = None
    for line in text.splitlines():
        stripped = line.strip()
        if not stripped or stripped.startswith("#"):
            continue
        m = re.match(r"^([A-Za-z0-9][A-Za-z0-9._-]*)==([^ #]+)", stripped)
        if m:
            current = m.group(1)
            active.append(current)
        elif "--hash=" in stripped:
            current = None
    # A simple package-level check: each active pin must have a hash before
    # the next active pin or end of file.
    missing = []
    for package in active:
        pattern = rf"(?ms)^{re.escape(package)}==[^\\n]+\\n(?:(?!^[A-Za-z0-9][A-Za-z0-9._-]*==).)*?--hash="
        if not re.search(pattern, text):
            missing.append(package)
    print(f"{path}: active_pins={len(active)} missing_hashes={len(missing)}")
    if missing:
        print("  " + ", ".join(missing))
PY

printf '%s\n' '--- generation command sites ---'
rg -n -C 3 '_pip_compile\([^)]*txt_path|_pip_compile\([^)]*out_txt|_pip_compile\([^)]*build_txt|_pip_compile\(' \
  openshift/hack/generate_requirements.py

Repository: openshift/ansible-operator-plugins

Length of output: 17725


Enable hash checking for generated build requirements.

openshift/hack/generate_requirements.py generates and rewrites build requirement files without --generate-hashes. openshift/install-ansible.sh installs them without --require-hashes. Update both so regenerated artifacts contain hashes and pip enforces them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshift/requirements-build.txt` around lines 7 - 8, Update
openshift/hack/generate_requirements.py to generate build requirement files with
hashes by enabling its --generate-hashes option, and update
openshift/install-ansible.sh to pass --require-hashes when installing those
requirements. Ensure regenerated artifacts contain hashes and pip enforces them.

Source: Path instructions

@mytreya-rh

Copy link
Copy Markdown
Contributor Author

/retest

@mytreya-rh mytreya-rh changed the title Discover transitive build deps missed by pip_find_builddeps.py ART-22058: Discover transitive build deps missed by pip_find_builddeps.py Aug 13, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Aug 13, 2026
@openshift-ci-robot

openshift-ci-robot commented Aug 13, 2026

Copy link
Copy Markdown

@mytreya-rh: This pull request references ART-22058 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

The ART hermetic build for openshift-enterprise-ansible-operator has been failing since Aug 10 (9 consecutive build_error runs) with:

Collecting setuptools
 Using cached setuptools-82.0.1-py3-none-any.whl
ERROR: Could not find a version that satisfies the requirement calver

Root cause: trove-classifiers (pulled into requirements-build.txt only as a transitive dependency of hatchling) declares calver in its own pyproject.toml [build-system].requires. pip_find_builddeps.py never scans it — Stage 3 of generate_requirements.py only runs on Pipfile.lock's runtime packages, not on second-order build tools pulled in by pip-compile. Even pointed at it directly, pip_find_builddeps.py couldn't discover this, since it relies on pip's own dependency-report machinery, which doesn't surface [build-system] requirements for a package that already has a wheel (pypa/pip#7863). Hermeto/Cachi2 defaults to source-only prefetching, so every package — wheel or not — is actually built from its sdist inside the hermetic sandbox, invoking that requirement regardless.

Fix: Add Stage 4b to generate_requirements.py. After phase-splitting, it iteratively fetches each newly-resolved (previously unscanned) package's pyproject.toml directly from its sdist on PyPI — bypassing pip entirely — and injects any undeclared build-system requirement into the owning phase, converging to a fixed point. This is fully dynamic (no package names hardcoded), so it will catch the same class of gap for any future package, not just calver/trove-classifiers.

  • openshift/hack/generate_requirements.py: new _pyproject_build_system_requires() helper and stage4b_close_transitive_build_deps() stage, wired in after Stage 4.
  • openshift/hack/generate_requirements.md: new "Stage 4b" section documenting the problem and fix.
  • openshift/Dockerfile.requirements: updated stage comment list.
  • openshift/requirements-build.txt: regenerated — adds calver==2025.10.20 (the fix) and poetry-core (needed to build hatchling's new tomlkit dependency from source); other changes are routine pip-compile version drift (hatchling/wheel/vcs-versioning bumps) unrelated to this fix.

Test plan

  • Unit-tested _pyproject_build_system_requires directly against trove-classifiers (returns ['setuptools', 'calver']) and edge cases (missing package, no [build-system] table).
  • Isolated integration test with a real pip-tools venv reproduced the bug (hatchling → trove-classifiers with no calver) and confirmed Stage 4b's loop discovers and injects it correctly.
  • Full end-to-end make -f openshift/Makefile generate-requirements run (fresh --no-cache container build): Stage 4b fires twice, adds calver/poetry-core, converges cleanly; Stage 5/6 pass with no warnings.
  • Diff against committed files is minimal: only requirements-build.txt changed (the new packages plus expected drift); requirements.txt, requirements-pre-build.txt, requirements-build1.txt, Pipfile.lock untouched.
  • go build ./... and go vet ./... pass (no Go code touched).

Made with Cursor

Summary by CodeRabbit

  • New Features

  • Build requirements generation now discovers transitive build dependencies automatically.

  • Requirements are iteratively updated until the dependency set is complete, with safeguards for convergence and recoverable failures.

  • Documentation

  • Added guidance describing the expanded generation workflow, performance considerations, and dependency discovery behavior.

  • Chores

  • Updated build tooling dependencies and versions to support the enhanced requirements-generation process.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@chiragkyal chiragkyal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed over Slack, we need to revisit the automation approach again to make it robust. Approving this PR to make room for ART build, with a hope to fix the breaking build.

/lgtm
/approve

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 14, 2026
@openshift-ci

openshift-ci Bot commented Aug 14, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: chiragkyal, mytreya-rh

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [chiragkyal,mytreya-rh]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@chiragkyal

Copy link
Copy Markdown
Member

/verified by ci

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Aug 14, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@chiragkyal: This PR has been marked as verified by ci.

Details

In response to this:

/verified by ci

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Aug 14, 2026

Copy link
Copy Markdown

@mytreya-rh: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
openshift-merge-bot Bot merged commit 2e572ad into openshift:main Aug 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants