Skip to content
26 changes: 21 additions & 5 deletions backends/pixi-build-ros/src/pixi_build_ros/ros_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def generate_recipe(
package_requirements.run.append(ItemPackageDependency(name=backend_config.distro.ros_distro_mutex_name))

# Merge package requirements into the model requirements
# raise ValueError(f"model: {generated_recipe.recipe.requirements}, package_requirements: {package_requirements}")
requirements = merge_requirements(generated_recipe.recipe.requirements, package_requirements)
generated_recipe.recipe.requirements = requirements

Expand Down Expand Up @@ -172,26 +173,34 @@ def merge_unique_items(
) -> list[ItemPackageDependency]:
"""Merge unique items from source into target."""

# raise ValueError(f"model: {model}, package: {package}")

def _find_matching(list_to_find: list[ItemPackageDependency], name: str) -> ItemPackageDependency | None:
for dep in list_to_find:
if dep.concrete.package_name == name:
return dep
else:
return None

def _merge_specs(spec1: str, spec2: str, package_name: str) -> str:
def _normalize_spec(spec: str | None, package_name: str) -> str:
"""Normalize a spec by removing package name and handling None."""
if not spec:
return ""
return spec.removeprefix(package_name).strip()

def _merge_specs(spec1: str | None, spec2: str | None, package_name: str) -> str:
# remove the package name
version_spec1 = spec1.removeprefix(package_name).strip()
version_spec2 = spec2.removeprefix(package_name).strip()
version_spec1 = _normalize_spec(spec1, package_name)
version_spec2 = _normalize_spec(spec2, package_name)

if " " in version_spec1 or " " in version_spec2:
raise ValueError(f"{version_spec1}, or {version_spec2} contains spaces, cannot merge specifiers.")

# early out with *, empty or ==
if version_spec1 in ["*", ""] or "==" in version_spec2 or version_spec1 == version_spec2:
return spec2
return spec2 or ""
if version_spec2 in ["*", ""] or "==" in version_spec1:
return spec1
return spec1 or ""
return package_name + " " + ",".join([version_spec1, version_spec2])

result: list[ItemPackageDependency] = []
Expand All @@ -203,6 +212,13 @@ def _merge_specs(spec1: str, spec2: str, package_name: str) -> str:
item_in_result = _find_matching(result, item.concrete.package_name)
if not item_in_result:
result.append(item)
elif item_in_result.concrete.is_source:
# If existing dependency is source, don't merge - keep the source one
continue
elif item.concrete.is_source:
# If new item is source, replace the existing one
result = [dep for dep in result if dep.concrete.package_name != item.concrete.package_name]
result.append(item)
else:
new_dep = ItemPackageDependency(
name=_merge_specs(
Expand Down
7 changes: 7 additions & 0 deletions backends/pixi-build-ros/tests/test_spec_merging.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ def test_specs_with_spaces():
with pytest.raises(ValueError) as exc:
merge_unique_items(list1, list2)
assert "contains spaces" in str(exc)


def test_specs_with_none():
Copy link
Contributor

Choose a reason for hiding this comment

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

can we maybe add a test with source usecase?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added one

list1 = [ItemPackageDependency("ros-noetic")]
list2 = [ItemPackageDependency("ros-noetic <=2.0,<3.0")]
result = merge_unique_items(list1, list2)
assert result[0].concrete.binary_spec == list2[0].concrete.binary_spec
Loading