1313from .metadata_provider import ROSPackageXmlMetadataProvider
1414from pixi_build_backend .types .intermediate_recipe import Script , ConditionalRequirements
1515
16- from pixi_build_backend .types .item import ItemPackageDependency
16+ from pixi_build_backend .types .item import ItemPackageDependency , VecItemPackageDependency
1717from pixi_build_backend .types .platform import Platform
1818from pixi_build_backend .types .project_model import ProjectModelV1
1919from pixi_build_backend .types .python_params import PythonParams
2020
2121from .build_script import BuildScriptContext , BuildPlatform
22- from .distro import Distro
2322from .utils import (
2423 get_build_input_globs ,
2524 package_xml_to_conda_requirements ,
@@ -46,13 +45,9 @@ def generate_recipe(
4645 backend_config : ROSBackendConfig = ROSBackendConfig .model_validate (
4746 config , context = {"manifest_root" : manifest_root }
4847 )
49-
50- # Setup ROS distro first
51- distro = Distro (backend_config .distro )
52-
5348 # Create metadata provider for package.xml
5449 package_xml_path = manifest_root / "package.xml"
55- metadata_provider = ROSPackageXmlMetadataProvider (str (package_xml_path ), distro )
50+ metadata_provider = ROSPackageXmlMetadataProvider (str (package_xml_path ), backend_config . distro . name )
5651
5752 # Create base recipe from model with metadata provider
5853 generated_recipe = GeneratedRecipe .from_model (model , metadata_provider )
@@ -75,7 +70,9 @@ def generate_recipe(
7570 )
7671
7772 # Get requirements from package.xml
78- package_requirements = package_xml_to_conda_requirements (package_xml , distro , host_platform , package_map_data )
73+ package_requirements = package_xml_to_conda_requirements (
74+ package_xml , backend_config .distro , host_platform , package_map_data
75+ )
7976
8077 # Add standard dependencies
8178 build_deps = [
@@ -106,6 +103,10 @@ def generate_recipe(
106103 for dep in host_deps :
107104 package_requirements .host .append (ItemPackageDependency (name = dep ))
108105
106+ # add a simple default host and run dependency on the ros{2}-distro-mutex
107+ package_requirements .host .append (ItemPackageDependency (name = backend_config .distro .ros_distro_mutex_name ))
108+ package_requirements .run .append (ItemPackageDependency (name = backend_config .distro .ros_distro_mutex_name ))
109+
109110 # Merge package requirements into the model requirements
110111 requirements = merge_requirements (generated_recipe .recipe .requirements , package_requirements )
111112 generated_recipe .recipe .requirements = requirements
@@ -114,7 +115,9 @@ def generate_recipe(
114115 build_platform = BuildPlatform .current ()
115116
116117 # Generate build script
117- build_script_context = BuildScriptContext .load_from_template (package_xml , build_platform , manifest_root , distro )
118+ build_script_context = BuildScriptContext .load_from_template (
119+ package_xml , build_platform , manifest_root , backend_config .distro
120+ )
118121 build_script_lines = build_script_context .render ()
119122
120123 generated_recipe .recipe .build .script = Script (
@@ -156,27 +159,60 @@ def merge_requirements(
156159 """Merge two sets of requirements."""
157160 merged = ConditionalRequirements ()
158161
159- # The model requirements are the base, coming from the pixi manifest
160- # We need to only add the names for non-existing dependencies
161- def merge_unique_items (
162- model : list [ItemPackageDependency ],
163- package : list [ItemPackageDependency ],
164- ) -> list [ItemPackageDependency ]:
165- """Merge unique items from source into target."""
166- result = model
167-
168- for item in package :
169- package_names = [i .concrete .package_name for i in model if i .concrete ]
170-
171- if item .concrete is not None and item .concrete .package_name not in package_names :
172- result .append (item )
173- if str (item .template ) not in [str (i .template ) for i in model ]:
174- result .append (item )
175- return result
176-
177162 merged .host = merge_unique_items (model_requirements .host , package_requirements .host )
178163 merged .build = merge_unique_items (model_requirements .build , package_requirements .build )
179164 merged .run = merge_unique_items (model_requirements .run , package_requirements .run )
180165
181166 # If the dependency is of type Source in one of the requirements, we need to set them to Source for all variants
182167 return merged
168+
169+
170+ def merge_unique_items (
171+ model : list [ItemPackageDependency ] | VecItemPackageDependency ,
172+ package : list [ItemPackageDependency ] | VecItemPackageDependency ,
173+ ) -> list [ItemPackageDependency ]:
174+ """Merge unique items from source into target."""
175+
176+ def _find_matching (list_to_find : list [ItemPackageDependency ], name : str ) -> ItemPackageDependency | None :
177+ for dep in list_to_find :
178+ if dep .concrete .package_name == name :
179+ return dep
180+ else :
181+ return None
182+
183+ def _merge_specs (spec1 : str , spec2 : str , package_name : str ) -> str :
184+ # remove the package name
185+ version_spec1 = spec1 .removeprefix (package_name ).strip ()
186+ version_spec2 = spec2 .removeprefix (package_name ).strip ()
187+
188+ if " " in version_spec1 or " " in version_spec2 :
189+ raise ValueError (f"{ version_spec1 } , or { version_spec2 } contains spaces, cannot merge specifiers." )
190+
191+ # early out with *, empty or ==
192+ if version_spec1 in ["*" , "" ] or "==" in version_spec2 or version_spec1 == version_spec2 :
193+ return spec2
194+ if version_spec2 in ["*" , "" ] or "==" in version_spec1 :
195+ return spec1
196+ return package_name + " " + "," .join ([version_spec1 , version_spec2 ])
197+
198+ result : list [ItemPackageDependency ] = []
199+ templates_in_model = [str (i .template ) for i in model ]
200+ for item in list (model ) + list (package ):
201+ # It's concrete (i.e. no template)
202+ if item .concrete is not None :
203+ # It does not exist yet in model
204+ item_in_result = _find_matching (result , item .concrete .package_name )
205+ if not item_in_result :
206+ result .append (item )
207+ else :
208+ new_dep = ItemPackageDependency (
209+ name = _merge_specs (
210+ item_in_result .concrete .binary_spec , item .concrete .binary_spec , item .concrete .package_name
211+ )
212+ )
213+ result .remove (item_in_result )
214+ result .append (new_dep )
215+
216+ elif str (item .template ) not in templates_in_model :
217+ result .append (item )
218+ return result
0 commit comments