From 9deb753ef79c9b607502a0913142884332619582 Mon Sep 17 00:00:00 2001 From: Bill Dolinar Date: Sat, 22 Aug 2026 23:47:19 -0600 Subject: [PATCH] Ask for a wheel only on the leg that builds one The GitHub workflow passed `--wheel-dir wheelhouse` to build.py on every matrix leg. `[matrix].pybind_build_types` defaults to `["Release"]`, so a Debug leg builds no pybind package -- and since 2.22.0 build.py treats `--wheel-dir` as a request it must satisfy, exiting 1 when no complete set of wheels comes out. That turned every Debug leg of every GitHub consumer red the moment it picked up 2.22.0, which is what happened to xmsgrid on 2026-08-21 and to xmsmesher on 2026-08-22: >> All configurations built successfully. >> No pybind package found to extract. error: --wheel-dir wheelhouse was given but no complete set of wheels was extracted; see the message above. Gate the flag on `matrix.build_type == 'Release'`, which is how repair, artifact upload and deploy in the same workflow are already gated -- and which `generate_ci` already relies on when it rejects a GitHub build.toml whose `pybind_build_types` excludes Release. A library that also names Debug loses nothing: its Debug wheel was built and discarded before. The Windows `--skip-dependency-libs` companion moves inside the gate with it. build.py reads that flag only within its `if args.wheel_dir` branch, so leaving it outside would put a flag on the Debug command line that reads as staging control and does nothing. The guard itself is left alone. `--wheel-dir` meaning "I require wheels" is the deliberate behavior; the defect was asking for them where none exist. GitLab is unaffected -- its build job carries no `build_type` filter, so the full matrix runs and the Release pybind configuration is always present. Consumers need `xmsconan ci` re-run and the result committed to pick this up; the workflow is generated, not floated. --- docs/USAGE.md | 3 +- tests/test_ci_file_generator.py | 49 +++++++++++++++++++ .../ci_templates/github-ci.yaml.jinja | 15 ++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index 04b2865..1d1783d 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -515,7 +515,7 @@ The generated jobs follow the pattern: 1. **Setup Python + Conan** (`xmsconan_conan_setup --remote-url … --login`) 2. **Generate build files** (`xmsconan_gen --version …`) -3. **Build** (`python build.py --filter='{"build_type": ""}' --wheel-dir wheelhouse --artifacts-dir test_artifacts`) +3. **Build** (`python build.py --filter='{"build_type": ""}' --artifacts-dir test_artifacts`, plus `--wheel-dir wheelhouse` on Release — §10.1) 4. **Repair wheel** on Release (`xmsconan_wheel_repair --wheel-dir wheelhouse`) 5. **On tag pushes:** `xmsconan_wheel_deploy` and `xmsconan_conan_deploy … --upload` @@ -526,6 +526,7 @@ The generated jobs follow the pattern: - Job `name:` carries the version on any platform that fans out (`GCC-13 (Release, 3.14, Linux)`). GitHub uses an explicit `name:` verbatim and only auto-appends matrix values when none is given, so without this two legs would share one status-check name — ambiguous in the checks list and in branch-protection matching. A single-version platform keeps its original name, so existing required checks keep matching. - **Windows** matrix: `build_type × compiler-version × python-version=ci_python_versions`. - Wheel artifacts carry `-py${{ matrix.python-version }}` on any platform that fans out; a single-version platform keeps its bare `wheel-${{ runner.os }}` name. +- **`--wheel-dir` is passed on the Release leg only.** `[matrix].pybind_build_types` defaults to `["Release"]` (§5.4.1), so a Debug leg builds no pybind package — and `build.py` treats `--wheel-dir` as a request it must satisfy, exiting 1 when no complete set of wheels comes out (§9.1). Passing it on every leg therefore failed every Debug leg; it did exactly that in xmsconan 2.22.0–2.22.2, so a repo generated by one of those needs `xmsconan ci` re-run. Repair, artifact upload and deploy are already gated the same way, so a library that also names `Debug` loses no wheel it was publishing. - Linux containers resolve to `conan-gcc13-py${{ matrix.python-version }}:latest`. - `flake` deliberately stays on a single hardcoded interpreter — linting is ABI-independent, and pinning it keeps lint results identical across repos. - Third-party actions are referenced by **commit SHA**, with the tag in a trailing comment (`uses: nelonoel/branch-name@1ea5c86… # v1.0.1`). A tag is a movable ref in a repository Aquaveo does not control, and the job it runs in holds the Conan and devpi secrets. `actions/*` stays on tags — a compromise of GitHub's own namespace is a compromise of the runner regardless. To move a pin, resolve the new tag with `gh api repos///commits/ --jq .sha` and edit the **template**. `test_github_ci_pins_third_party_actions_to_a_sha` fails on any third-party action added by tag. diff --git a/tests/test_ci_file_generator.py b/tests/test_ci_file_generator.py index 93987da..1f540d7 100644 --- a/tests/test_ci_file_generator.py +++ b/tests/test_ci_file_generator.py @@ -530,6 +530,55 @@ def test_ci_pins_conan_version(ci_toml, tmp_path): assert [line for line in conan_installs if '"conan~=' not in line] == [] +def _build_step_run(job): + """The ``run:`` of one job's "Build the Conan Packages" step.""" + for step in job["steps"]: + if step.get("name") == "Build the Conan Packages": + return step["run"] + raise AssertionError("job has no 'Build the Conan Packages' step") + + +@pytest.mark.parametrize("job_name", ["mac", "linux", "windows"]) +def test_github_build_step_requests_a_wheel_only_on_release(ci_toml, tmp_path, job_name): + """``--wheel-dir`` reaches build.py on the Release leg alone. + + ``[matrix].pybind_build_types`` defaults to Release, so a Debug leg builds + no pybind package -- and build.py treats ``--wheel-dir`` as a request it + must satisfy, exiting 1 when no complete set of wheels comes out. Passing + the flag unconditionally therefore failed every Debug leg of every + consumer, which is what it did between xmsconan 2.22.0 and this fix. + + Asserted per job rather than over the whole document so a regression names + the platform that lost the gate. + """ + run = _build_step_run(_github_jobs(ci_toml, tmp_path)[job_name]) + + assert run.count("--wheel-dir") == 1 + assert "${{ matrix.build_type == 'Release' && '--wheel-dir wheelhouse" in run + + +def test_github_windows_skip_dependency_libs_stays_inside_the_wheel_gate(tmp_path): + """The unrepaired-Windows companion flag is gated with ``--wheel-dir``. + + ``--skip-dependency-libs`` is read only inside build.py's ``if + args.wheel_dir`` branch, so leaving it outside the gate would put a flag on + the Debug command line that reads as staging control and does nothing. + """ + toml_file = tmp_path / "build.toml" + toml_file.write_text( + 'library_name = "xmscore"\ndescription = "Core library"\nci_type = "github"\n' + "\n[ci]\nwindows_wheel_repair = false\n", + encoding="utf-8", + ) + + run = _build_step_run(_github_jobs(toml_file, tmp_path)["windows"]) + + assert ( + "${{ matrix.build_type == 'Release' && " + "'--wheel-dir wheelhouse --skip-dependency-libs' || '' }}" in run + ) + + def test_github_ci_includes_artifacts_dir_flag(ci_toml, tmp_path): """Rendered GitHub CI build commands include --artifacts-dir test_artifacts.""" output_dir = tmp_path / "output" diff --git a/xmsconan/generator_tools/ci_templates/github-ci.yaml.jinja b/xmsconan/generator_tools/ci_templates/github-ci.yaml.jinja index 38d781b..7793428 100644 --- a/xmsconan/generator_tools/ci_templates/github-ci.yaml.jinja +++ b/xmsconan/generator_tools/ci_templates/github-ci.yaml.jinja @@ -13,6 +13,13 @@ # (`gh api repos///commits/ --jq .sha`) and editing the # template -- not the generated file. # +# Every wheel step -- `--wheel-dir` on the build, then repair, artifact +# upload and deploy -- runs on the Release leg only. +# `[matrix].pybind_build_types` defaults to Release alone, so a Debug leg +# builds no pybind package, and build.py treats `--wheel-dir` as a request +# it must satisfy: it exits 1 when no complete set of wheels comes out. +# Asking a Debug leg for a wheel fails the leg instead of producing one. +# # Generated by xmsconan_ci — do not edit manually. name: << display_name >>-CI @@ -159,7 +166,7 @@ jobs: run: xmsconan_gen --version ${{ env.XMS_VERSION }} build.toml # Build the Conan Package - name: Build the Conan Packages - run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" --wheel-dir wheelhouse --artifacts-dir test_artifacts" + run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" ${{ matrix.build_type == 'Release' && '--wheel-dir wheelhouse' || '' }} --artifacts-dir test_artifacts" shell: bash - name: Upload test artifacts uses: actions/upload-artifact@v4 @@ -302,7 +309,7 @@ jobs: run: xmsconan_gen --version ${{ env.XMS_VERSION }} build.toml # Build the Conan Package - name: Build the Conan Packages - run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" --wheel-dir wheelhouse --artifacts-dir test_artifacts" + run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" ${{ matrix.build_type == 'Release' && '--wheel-dir wheelhouse' || '' }} --artifacts-dir test_artifacts" shell: bash - name: Upload test artifacts uses: actions/upload-artifact@v4 @@ -446,7 +453,7 @@ jobs: run: xmsconan_gen --version ${{ env.XMS_VERSION }} build.toml # Build the Conan Package - name: Build the Conan Packages - run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" --wheel-dir wheelhouse --artifacts-dir test_artifacts" + run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" ${{ matrix.build_type == 'Release' && '--wheel-dir wheelhouse' || '' }} --artifacts-dir test_artifacts" shell: bash - name: Upload test artifacts uses: actions/upload-artifact@v4 @@ -598,7 +605,7 @@ jobs: run: xmsconan_gen --version ${{ env.XMS_VERSION }} build.toml # Build the Conan Package - name: Build the Conan Packages - run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" --wheel-dir wheelhouse<% if not ci_windows_wheel_repair %> --skip-dependency-libs<% endif %> --artifacts-dir test_artifacts" + run: "python build.py --filter=\"{\\\"build_type\\\": \\\"${{ matrix.build_type }}\\\"}\" ${{ matrix.build_type == 'Release' && '--wheel-dir wheelhouse<% if not ci_windows_wheel_repair %> --skip-dependency-libs<% endif %>' || '' }} --artifacts-dir test_artifacts" shell: cmd - name: Upload test artifacts uses: actions/upload-artifact@v4