From 3d9616f25114f1abb7b0943adbe0ff70e587b16a Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Sat, 10 Oct 2026 19:39:08 +1000 Subject: [PATCH] generate_gha: omit empty needs for first-stage jobs actionlint rejects `needs: []` ("needs section should not be empty"), which every job of the first stage got. Only emit needs when there is a previous stage. Co-Authored-By: Claude Opus 5.5 --- vinca/generate_gha.py | 6 ++++-- vinca/test_generate_gha.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/vinca/generate_gha.py b/vinca/generate_gha.py index 83f8cc3..8d31b78 100644 --- a/vinca/generate_gha.py +++ b/vinca/generate_gha.py @@ -271,7 +271,8 @@ def build_unix_pipeline( "name": pretty_stage_name, "runs-on": runs_on, "strategy": {"fail-fast": False}, - "needs": prev_batch_keys, + # actionlint rejects an empty needs list, so first-stage jobs omit it + **({"needs": prev_batch_keys} if prev_batch_keys else {}), "steps": steps, } @@ -410,7 +411,8 @@ def build_win_pipeline( "name": pretty_stage_name, "runs-on": vm_imagename, "strategy": {"fail-fast": False}, - "needs": prev_batch_keys, + # actionlint rejects an empty needs list, so first-stage jobs omit it + **({"needs": prev_batch_keys} if prev_batch_keys else {}), "env": { "CONDA_BLD_PATH": "C:\\\\bld\\\\", "VINCA_CUSTOM_CMAKE_BUILD_DIR": "C:\\\\x\\\\", diff --git a/vinca/test_generate_gha.py b/vinca/test_generate_gha.py index 5bae6e9..3c6f5ca 100644 --- a/vinca/test_generate_gha.py +++ b/vinca/test_generate_gha.py @@ -120,3 +120,21 @@ def test_win_pipeline_requires_the_repository_build_script(tmp_path, monkeypatch build_win_pipeline( [[["ros2-rclcpp"]]], "buildbranch_win", outfile=tmp_path / "win.yml" ) + + +def test_only_later_stages_declare_needs(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + (tmp_path / ".scripts").mkdir() + (tmp_path / ".scripts" / "build_win.bat").write_text("echo build\n") + stages = [[["ros2-a"], ["ros2-b"]], [["ros2-c"]]] + yaml = pytest.importorskip("yaml") + + build_unix_pipeline(stages, "branch", outfile=tmp_path / "linux.yml") + build_win_pipeline(stages, "branch", outfile=tmp_path / "win.yml") + + for outfile in ("linux.yml", "win.yml"): + jobs = yaml.safe_load((tmp_path / outfile).read_text())["jobs"] + # actionlint rejects `needs: []` + assert "needs" not in jobs["stage_0_job_0"] + assert "needs" not in jobs["stage_0_job_1"] + assert jobs["stage_1_job_2"]["needs"] == ["stage_0_job_0", "stage_0_job_1"]