Skip to content

Commit d09affe

Browse files
authored
feat(ros): add mapping file to globs (#418)
1 parent cee6785 commit d09affe

File tree

4 files changed

+92
-6
lines changed

4 files changed

+92
-6
lines changed

backends/pixi-build-ros/src/pixi_build_ros/config.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ def _parse_str_as_abs_path(value: str | Path, manifest_root: Path) -> Path:
2525
class PackageMappingSource:
2626
"""Describes where additional package mapping data comes from."""
2727

28-
def __init__(self, mapping: dict[str, PackageMapEntry]):
28+
def __init__(self, mapping: dict[str, PackageMapEntry], source_file: Path | None = None):
2929
if mapping is None:
3030
raise ValueError("PackageMappingSource mapping cannot be null.")
3131
if not isinstance(mapping, dict):
3232
raise TypeError("PackageMappingSource mapping must be a dictionary.")
3333
# Copy to keep the source immutable for callers.
3434
self.mapping: dict[str, PackageMapEntry] = dict(mapping)
35+
# Track the source file path if this came from a file
36+
self.source_file: Path | None = source_file
3537

3638
@classmethod
3739
def from_mapping(cls, mapping: dict[str, PackageMapEntry]) -> "PackageMappingSource":
3840
"""Create a source directly from a mapping dictionary."""
39-
return cls(mapping)
41+
return cls(mapping, source_file=None)
4042

4143
@classmethod
4244
def from_file(cls, file_path: str | Path) -> "PackageMappingSource":
@@ -48,11 +50,15 @@ def from_file(cls, file_path: str | Path) -> "PackageMappingSource":
4850
data = yaml.safe_load(f) or {}
4951
if not isinstance(data, dict):
5052
raise TypeError("Expected package map file to contain a dictionary.")
51-
return cls(data)
53+
return cls(data, source_file=path)
5254

5355
def get_package_mapping(self) -> dict[str, PackageMapEntry]:
5456
return dict(self.mapping)
5557

58+
def get_source_file(self) -> Path | None:
59+
"""Return the source file path if this mapping came from a file."""
60+
return self.source_file
61+
5662

5763
class ROSBackendConfig(pydantic.BaseModel, extra="forbid", arbitrary_types_allowed=True):
5864
"""ROS backend configuration."""
@@ -78,6 +84,14 @@ def is_noarch(self) -> bool:
7884
"""Whether to build a noarch package or a platform-specific package."""
7985
return self.noarch is None or self.noarch
8086

87+
def get_package_mapping_file_paths(self) -> list[Path]:
88+
"""Get all file paths from package mappings that came from files."""
89+
file_paths = []
90+
for source in self.extra_package_mappings:
91+
if source_file := source.get_source_file():
92+
file_paths.append(source_file)
93+
return file_paths
94+
8195
@pydantic.field_validator("distro", mode="before")
8296
@classmethod
8397
def _parse_distro(cls, value: str | Distro) -> Distro:

backends/pixi-build-ros/src/pixi_build_ros/metadata_provider.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,22 @@ def __init__( # type: ignore[no-untyped-def] # no typing for args and kwargs
4949
package_xml_path: str,
5050
*args,
5151
extra_input_globs: list[str] | None = None,
52+
package_mapping_files: list[str] | None = None,
5253
**kwargs,
5354
):
5455
"""
5556
Initialize the metadata provider with a package.xml file path.
5657
5758
Args:
5859
package_xml_path: Path to the package.xml file
60+
extra_input_globs: Additional glob patterns to include
61+
package_mapping_files: Package mapping file paths to track as inputs
5962
"""
6063
super().__init__(*args, **kwargs)
6164
self.package_xml_path = package_xml_path
6265
self._package_data: PackageData | None = None
6366
self._extra_input_globs = list(extra_input_globs or [])
67+
self._package_mapping_files = list(package_mapping_files or [])
6468
# Early load the package.xml data to ensure it's valid
6569
_ = self._package_xml_data
6670

@@ -170,7 +174,8 @@ def repository(self) -> str | None:
170174
def input_globs(self) -> list[str]:
171175
"""Return input globs that affect this metadata provider."""
172176
base_globs = ["package.xml", "CMakeLists.txt", "setup.py", "setup.cfg"]
173-
return list(set(base_globs + self._extra_input_globs))
177+
all_globs = base_globs + self._extra_input_globs + self._package_mapping_files
178+
return list(set(all_globs))
174179

175180

176181
class ROSPackageXmlMetadataProvider(PackageXmlMetadataProvider):
@@ -187,15 +192,22 @@ def __init__(
187192
distro_name: str | None = None,
188193
*,
189194
extra_input_globs: list[str] | None = None,
195+
package_mapping_files: list[str] | None = None,
190196
):
191197
"""
192198
Initialize the ROS metadata provider.
193199
194200
Args:
195201
package_xml_path: Path to the package.xml file
196-
distro: ROS distro. If None, will use the base package name without distro prefix.
202+
distro_name: ROS distro. If None, will use the base package name without distro prefix.
203+
extra_input_globs: Additional glob patterns to include
204+
package_mapping_files: Package mapping file paths to track as inputs
197205
"""
198-
super().__init__(package_xml_path, extra_input_globs=extra_input_globs)
206+
super().__init__(
207+
package_xml_path,
208+
extra_input_globs=extra_input_globs,
209+
package_mapping_files=package_mapping_files,
210+
)
199211
self._distro_name: str | None = distro_name
200212

201213
def name(self) -> str | None:

backends/pixi-build-ros/src/pixi_build_ros/ros_generator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,13 @@ def generate_recipe(
4848
)
4949
# Create metadata provider for package.xml
5050
package_xml_path = manifest_root / "package.xml"
51+
# Get package mapping file paths to include in input globs
52+
package_mapping_files = [str(path) for path in backend_config.get_package_mapping_file_paths()]
5153
metadata_provider = ROSPackageXmlMetadataProvider(
5254
str(package_xml_path),
5355
backend_config.distro.name,
5456
extra_input_globs=list(backend_config.extra_input_globs or []),
57+
package_mapping_files=package_mapping_files,
5558
)
5659

5760
# Create base recipe from model with metadata provider

backends/pixi-build-ros/tests/test_meta_data_provider.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,60 @@ def test_metadata_provider_raises_on_broken_xml(package_xmls: Path):
4141
# Verify the exception contains location information
4242
error = exc_info.value
4343
assert "Failed to parse package.xml" in str(error)
44+
45+
46+
def test_metadata_provider_includes_package_mapping_files_in_input_globs():
47+
"""Test that package mapping files from config are included in input_globs."""
48+
import tempfile
49+
import yaml
50+
from pixi_build_ros.config import ROSBackendConfig
51+
52+
with tempfile.TemporaryDirectory() as temp_dir:
53+
temp_path = Path(temp_dir)
54+
55+
# Create a package.xml inline
56+
package_xml_content = """<?xml version="1.0"?>
57+
<package format="2">
58+
<name>test_package</name>
59+
<version>1.0.0</version>
60+
<description>Test package</description>
61+
<maintainer email="test@test.com">Test</maintainer>
62+
<license>Apache-2.0</license>
63+
</package>
64+
"""
65+
package_xml_path = temp_path / "package.xml"
66+
package_xml_path.write_text(package_xml_content)
67+
68+
# Create a mapping file inline
69+
mapping_content = {"custom_package": {"conda": ["custom-conda-package"]}}
70+
mapping_file_path = temp_path / "custom_mapping.yaml"
71+
with open(mapping_file_path, "w") as f:
72+
yaml.dump(mapping_content, f)
73+
74+
# Create config with the mapping file
75+
config = {
76+
"distro": "noetic",
77+
"extra-package-mappings": [str(mapping_file_path)],
78+
}
79+
80+
backend_config = ROSBackendConfig.model_validate(config, context={"manifest_root": temp_path})
81+
82+
# Get package mapping file paths
83+
package_mapping_files = [str(path) for path in backend_config.get_package_mapping_file_paths()]
84+
85+
# Create metadata provider
86+
metadata_provider = ROSPackageXmlMetadataProvider(
87+
str(package_xml_path),
88+
distro_name="noetic",
89+
package_mapping_files=package_mapping_files,
90+
)
91+
92+
# Get input globs
93+
input_globs = metadata_provider.input_globs()
94+
95+
# Verify the mapping file is included
96+
assert str(mapping_file_path) in input_globs
97+
98+
# Verify base globs are still present
99+
assert "package.xml" in input_globs
100+
assert "CMakeLists.txt" in input_globs

0 commit comments

Comments
 (0)