From dbae211ffcd6a653fe2290828b317b7c089053b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Gro=C3=9Fmann?= Date: Tue, 10 Mar 2026 06:37:31 +0100 Subject: [PATCH] feat: add per-module max_rc for final link (default 0) Previously mvslink hardcoded RC>4 as failure threshold. Now each [[link.module]] can specify max_rc (default: 0, strict). Modules that need to tolerate linker warnings set max_rc = 4 explicitly. --- scripts/mbt/project.py | 2 ++ scripts/mvslink.py | 3 +-- tests/test_project.py | 48 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/scripts/mbt/project.py b/scripts/mbt/project.py index bb0481f..6c73439 100644 --- a/scripts/mbt/project.py +++ b/scripts/mbt/project.py @@ -58,6 +58,7 @@ class LinkModule: include: list[str] # own members to include explicitly dep_includes: dict # {dep_key: "*" | [member, ...]} for non-autocall deps setcode: str = "" # SETCODE statement value, e.g. "AC(1)" + max_rc: int = 0 # max acceptable RC for final link (0 = strict) @dataclass @@ -210,6 +211,7 @@ def _parse(cls, data: dict) -> "ProjectConfig": include=list(mod.get("include", ["@@CRT1", mod_name])), dep_includes=dep_includes, setcode=mod.get("setcode", ""), + max_rc=int(mod.get("max_rc", 0)), )) # Source directories — partial override is allowed diff --git a/scripts/mvslink.py b/scripts/mvslink.py index 572a224..ddf95b2 100644 --- a/scripts/mvslink.py +++ b/scripts/mvslink.py @@ -232,8 +232,7 @@ def main() -> int: _log_error(f"Failed to submit link job for {mod.name}: {e}") return EXIT_BUILD - # Full link: RC=4 (informational warning) may be acceptable with LET - if result.rc > 4: + if result.rc > mod.max_rc: log_file = _save_job_log(result, mod.name) _log_error(f"{mod.name} link failed (RC={result.rc})") _log(f"Job: {result.jobname} / {result.jobid}") diff --git a/tests/test_project.py b/tests/test_project.py index 7a0b404..98e318e 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -71,6 +71,7 @@ def test_link_modules(self): self.assertEqual(mod.options, ["RENT", "REUS"]) self.assertEqual(mod.include, ["@@CRT1", "HELLO"]) self.assertEqual(mod.setcode, "") + self.assertEqual(mod.max_rc, 0) def test_artifacts(self): self.assertFalse(self.config.artifact_headers) @@ -332,5 +333,52 @@ def test_setcode_default_empty(self): self.assertEqual(mod.setcode, "") +class TestLinkModuleMaxRc(unittest.TestCase): + + def _write(self, content: str) -> Path: + self._tmp = tempfile.mkdtemp() + p = Path(self._tmp) / "project.toml" + p.write_text(content, encoding="utf-8") + return p + + def tearDown(self): + import shutil + if hasattr(self, "_tmp"): + shutil.rmtree(self._tmp, ignore_errors=True) + + def test_max_rc_default_zero(self): + toml = ( + "[project]\nname=\"myapp\"\nversion=\"1.0.0\"\ntype=\"application\"\n" + "[mvs.build.datasets.ncalib]\n" + "suffix=\"NCALIB\"\ndsorg=\"PO\"\nrecfm=\"U\"\n" + "lrecl=0\nblksize=32760\nspace=[\"TRK\",5,2,5]\n" + "[mvs.build.datasets.syslmod]\n" + "suffix=\"LOAD\"\ndsorg=\"PO\"\nrecfm=\"U\"\n" + "lrecl=0\nblksize=32760\nspace=[\"TRK\",5,2,5]\n" + "[dependencies]\n\"mvslovers/crent370\" = \">=1.0.0\"\n" + "[link.module]\n" + "name=\"MYAPP\"\noptions=[\"RENT\"]\n" + ) + c = ProjectConfig.load(self._write(toml)) + self.assertEqual(c.link_modules[0].max_rc, 0) + + def test_max_rc_parsed(self): + toml = ( + "[project]\nname=\"myapp\"\nversion=\"1.0.0\"\ntype=\"application\"\n" + "[mvs.build.datasets.ncalib]\n" + "suffix=\"NCALIB\"\ndsorg=\"PO\"\nrecfm=\"U\"\n" + "lrecl=0\nblksize=32760\nspace=[\"TRK\",5,2,5]\n" + "[mvs.build.datasets.syslmod]\n" + "suffix=\"LOAD\"\ndsorg=\"PO\"\nrecfm=\"U\"\n" + "lrecl=0\nblksize=32760\nspace=[\"TRK\",5,2,5]\n" + "[dependencies]\n\"mvslovers/crent370\" = \">=1.0.0\"\n" + "[link.module]\n" + "name=\"MYAPP\"\noptions=[\"RENT\"]\n" + "max_rc=4\n" + ) + c = ProjectConfig.load(self._write(toml)) + self.assertEqual(c.link_modules[0].max_rc, 4) + + if __name__ == "__main__": unittest.main()