diff --git a/vinca/main.py b/vinca/main.py index 8aa17e2..96d210a 100644 --- a/vinca/main.py +++ b/vinca/main.py @@ -341,6 +341,16 @@ def conda_index_shadowed_packages(distro, vinca_conf): return names +def expected_build_number(default_build_number, pkg_name, vinca_conf): + """The build number a package of this configuration gets: the mutex has its own + (mutex_package.build_number, e.g. a new build at the same version), the others + the distribution's unless pkg_additional_info.yaml overrides it.""" + mutex_config = parse_mutex_package_config(vinca_conf) + if mutex_config and pkg_name == mutex_config["name"]: + return mutex_config["build_number"] + return get_pkg_build_number(default_build_number, pkg_name, vinca_conf) + + def get_selected_packages(distro, vinca_conf): selected_packages = set() skipped_packages = set() @@ -742,7 +752,7 @@ def main(): for _, pkg in all_pkgs.items(): is_built = False if selected_bn is not None: - pkg_build_number = get_pkg_build_number( + pkg_build_number = expected_build_number( selected_bn, pkg["name"], vinca_conf ) if pkg["build_number"] == pkg_build_number: diff --git a/vinca/mutex.py b/vinca/mutex.py index b69a76f..8efd305 100644 --- a/vinca/mutex.py +++ b/vinca/mutex.py @@ -14,7 +14,8 @@ from vinca.distro import Distro -_REQUIRED_FIELDS = ("name", "version", "upper_bound", "run_constraints") +# run_constraints is optional: a mutex only has to keep one distribution per environment +_REQUIRED_FIELDS = ("name", "version", "upper_bound") def parse_mutex_package_config(vinca_conf: dict[str, Any]) -> dict[str, Any] | None: @@ -102,7 +103,11 @@ def generate_mutex_package_recipe( "script": "", }, "requirements": { - "run_constraints": mutex_config["run_constraints"], + **( + {"run_constraints": mutex_config["run_constraints"]} + if mutex_config.get("run_constraints") + else {} + ), "run_exports": { "weak": [ f"${{{{ pin_subpackage('{name}', upper_bound='{mutex_config['upper_bound']}') }}}}" diff --git a/vinca/template.py b/vinca/template.py index 6873139..b4ab96c 100644 --- a/vinca/template.py +++ b/vinca/template.py @@ -131,8 +131,12 @@ def write_recipe(source, outputs, vinca_conf, distro, single_file=True): o, distro, vinca_conf ) + # an output's own build number (e.g. mutex_package.build_number) wins + # over the distribution's; pkg_additional_info.yaml overrides both meta["build"]["number"] = get_pkg_build_number( - vinca_conf.get("build_number", 0), package_name, vinca_conf + o.get("build", {}).get("number", vinca_conf.get("build_number", 0)), + package_name, + vinca_conf, ) meta["build"]["post_process"] = post_process_items diff --git a/vinca/test_mutex_build_number.py b/vinca/test_mutex_build_number.py new file mode 100644 index 0000000..6232e98 --- /dev/null +++ b/vinca/test_mutex_build_number.py @@ -0,0 +1,67 @@ +"""mutex_package.build_number: a new build of the mutex at the same version.""" + +from unittest.mock import Mock + +import yaml + +from vinca.main import expected_build_number +from vinca.mutex import generate_mutex_package_recipe +from vinca.template import write_recipe + + +def _conf(**extra): + conf = { + "ros_distro": "lyrical", + "build_number": 27, + "mutex_package": { + "name": "ros2-distro-mutex", + "version": "0.21.0", + "upper_bound": "x.x", + "build_number": 28, + }, + "_pkg_additional_info": {"rclcpp": {"build_number": 30}}, + "_tests": {}, + } + conf.update(extra) + return conf + + +def test_expected_build_number_of_the_mutex_is_its_own(): + assert expected_build_number(27, "ros2-distro-mutex", _conf()) == 28 + + +def test_expected_build_number_of_other_packages(): + conf = _conf() + assert expected_build_number(27, "ros2-rclcpp", conf) == 30 + assert expected_build_number(27, "ros2-std-msgs", conf) == 27 + + +def test_mutex_without_own_build_number_uses_the_distribution_one(): + conf = _conf() + del conf["mutex_package"]["build_number"] + assert expected_build_number(27, "ros2-distro-mutex", conf) == 27 + + +def test_mutex_recipe_keeps_its_build_number(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + conf = _conf() + distro = Mock() + distro.name = "lyrical" + recipe = generate_mutex_package_recipe(conf, distro) + write_recipe({}, [recipe], conf, distro, single_file=False) + meta = yaml.safe_load( + (tmp_path / "recipes" / "ros2-distro-mutex" / "recipe.yaml").read_text() + ) + assert meta["build"]["number"] == 28 + assert meta["build"]["string"] == "lyrical_28" + # no (empty) run_constraints in the recipe + assert "run_constraints" not in meta["requirements"] + + +def test_mutex_with_run_constraints_keeps_them(): + conf = _conf() + conf["mutex_package"]["run_constraints"] = ["libfoo 1.*"] + distro = Mock() + distro.name = "lyrical" + recipe = generate_mutex_package_recipe(conf, distro) + assert recipe["requirements"]["run_constraints"] == ["libfoo 1.*"]