Skip to content

Commit 6cc4abe

Browse files
authored
feat: automatic detection of ros backend for package.xml files (#4782)
1 parent 1d72d09 commit 6cc4abe

File tree

9 files changed

+259
-0
lines changed

9 files changed

+259
-0
lines changed

crates/pixi_build_discovery/src/backend_spec.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,25 @@ impl JsonRpcBackendSpec {
168168
})),
169169
}
170170
}
171+
172+
/// Constructs a new default instance for spawning a ROS build backend.
173+
pub fn default_ros_build(channel_config: &ChannelConfig) -> Self {
174+
const DEFAULT_BUILD_TOOL: &str = "pixi-build-ros";
175+
176+
let conda_forge_channel = Channel::from_name("conda-forge", channel_config).base_url;
177+
let backends_channel = Url::from_str("https://prefix.dev/pixi-build-backends")
178+
.unwrap()
179+
.into();
180+
181+
Self {
182+
name: DEFAULT_BUILD_TOOL.to_string(),
183+
command: CommandSpec::EnvironmentSpec(Box::new(EnvironmentSpec {
184+
requirement: (DEFAULT_BUILD_TOOL.parse().unwrap(), PixiSpec::any()),
185+
additional_requirements: Default::default(),
186+
constraints: Default::default(),
187+
channels: vec![conda_forge_channel, backends_channel],
188+
command: None,
189+
})),
190+
}
191+
}
171192
}

crates/pixi_build_discovery/src/discovery.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::{
2424

2525
const VALID_RECIPE_NAMES: [&str; 2] = ["recipe.yaml", "recipe.yml"];
2626
const VALID_RECIPE_DIRS: [&str; 2] = ["", "recipe"];
27+
const VALID_ROS_BACKEND_NAMES: [&str; 1] = ["package.xml"];
2728

2829
/// Describes a backend discovered for a given source location.
2930
#[derive(Debug, Clone)]
@@ -72,6 +73,8 @@ pub struct BackendInitializationParams {
7273
pub struct EnabledProtocols {
7374
/// Enable the rattler-build protocol.
7475
pub enable_rattler_build: bool,
76+
/// Enable the ROS protocol.
77+
pub enable_ros: bool,
7578
/// Enable the pixi protocol.
7679
pub enable_pixi: bool,
7780
}
@@ -81,6 +84,7 @@ impl Default for EnabledProtocols {
8184
fn default() -> Self {
8285
Self {
8386
enable_rattler_build: true,
87+
enable_ros: true,
8488
enable_pixi: true,
8589
}
8690
}
@@ -95,6 +99,9 @@ pub enum DiscoveryError {
9599
#[error("depending on a `{0}` file but the rattler-build protocol is not enabled")]
96100
UnsupportedRecipeYaml(String),
97101

102+
#[error("depending on a `{0}` file but the ros protocol is not enabled")]
103+
UnsupportedPackageXml(String),
104+
98105
#[error(transparent)]
99106
#[diagnostic(transparent)]
100107
FailedToDiscoverPackage(#[from] WorkspaceDiscoveryError),
@@ -140,6 +147,23 @@ impl DiscoveredBackend {
140147
.expect("the recipe must live somewhere");
141148
return Self::from_recipe(source_dir.to_path_buf(), source_path, channel_config);
142149
}
150+
151+
// If the user explicitly asked for a package.xml file
152+
if VALID_ROS_BACKEND_NAMES.contains(&source_file_name) {
153+
if !enabled_protocols.enable_ros {
154+
return Err(DiscoveryError::UnsupportedPackageXml(
155+
source_file_name.to_string(),
156+
));
157+
}
158+
let source_dir = source_path
159+
.parent()
160+
.expect("the package.xml must live somewhere");
161+
return Self::from_ros_package(
162+
source_dir.to_path_buf(),
163+
source_path,
164+
channel_config,
165+
);
166+
}
143167
}
144168

145169
// Try to discover a pixi project.
@@ -156,6 +180,13 @@ impl DiscoveredBackend {
156180
}
157181
}
158182

183+
// Try to discover as a ROS package.
184+
if enabled_protocols.enable_ros {
185+
if let Some(ros) = Self::discover_ros(source_path.clone(), channel_config)? {
186+
return Ok(ros);
187+
}
188+
}
189+
159190
Err(DiscoveryError::FailedToDiscover(
160191
source_path.to_string_lossy().to_string(),
161192
))
@@ -331,4 +362,47 @@ impl DiscoveredBackend {
331362
}
332363
Ok(None)
333364
}
365+
366+
/// Construct a new instance based on a specific `package.xml` file in the
367+
/// source directory.
368+
fn from_ros_package(
369+
source_dir: PathBuf,
370+
package_xml_absolute_path: PathBuf,
371+
channel_config: &ChannelConfig,
372+
) -> Result<Self, DiscoveryError> {
373+
debug_assert!(source_dir.is_absolute());
374+
debug_assert!(package_xml_absolute_path.is_absolute());
375+
Ok(Self {
376+
backend_spec: BackendSpec::JsonRpc(JsonRpcBackendSpec::default_ros_build(
377+
channel_config,
378+
)),
379+
init_params: BackendInitializationParams {
380+
workspace_root: source_dir.clone(),
381+
source: None,
382+
source_anchor: source_dir,
383+
manifest_path: package_xml_absolute_path,
384+
project_model: Some(ProjectModelV1::default()),
385+
configuration: None,
386+
target_configuration: None,
387+
},
388+
})
389+
}
390+
391+
/// Try to discover a ROS package.xml file in the repository.
392+
fn discover_ros(
393+
source_dir: PathBuf,
394+
channel_config: &ChannelConfig,
395+
) -> Result<Option<Self>, DiscoveryError> {
396+
for &package_file in VALID_ROS_BACKEND_NAMES.iter() {
397+
let package_path = source_dir.join(package_file);
398+
if package_path.is_file() {
399+
return Ok(Some(Self::from_ros_package(
400+
source_dir,
401+
package_path,
402+
channel_config,
403+
)?));
404+
}
405+
}
406+
Ok(None)
407+
}
334408
}

crates/pixi_build_discovery/tests/discovery.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,21 @@ fn test_direct_recipe() {
104104
assert_discover_snapshot ! ( & path);
105105
});
106106
}
107+
108+
#[test]
109+
fn test_direct_package_xml() {
110+
let path = dunce::canonicalize(discovery_directory().join("ros-package/package.xml")).unwrap();
111+
let source_path_regex = path
112+
.parent()
113+
.unwrap()
114+
.to_string_lossy()
115+
.replace(r"\", r"\\\\");
116+
insta::with_settings!({
117+
filters => vec![
118+
(source_path_regex.as_str(), "file://<ROOT>"),
119+
(r"\\", r"/"),
120+
],
121+
}, {
122+
assert_discover_snapshot ! ( & path);
123+
});
124+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
source: crates/pixi_build_discovery/tests/discovery.rs
3+
expression: backend
4+
---
5+
backend-spec:
6+
type: json-rpc
7+
name: pixi-build-ros
8+
command:
9+
type: environment-spec
10+
requirement:
11+
- pixi-build-ros
12+
- "*"
13+
channels:
14+
- "https://conda.anaconda.org/conda-forge"
15+
- "https://prefix.dev/pixi-build-backends"
16+
init-params:
17+
workspace-root: "file://<ROOT>/ros-package"
18+
source: ~
19+
source-anchor: "file://<ROOT>/ros-package"
20+
manifest-path: "file://<ROOT>/ros-package/package.xml"
21+
project-model:
22+
name: ~
23+
version: ~
24+
description: ~
25+
authors: ~
26+
license: ~
27+
licenseFile: ~
28+
readme: ~
29+
homepage: ~
30+
repository: ~
31+
documentation: ~
32+
targets: ~
33+
configuration: ~
34+
target-configuration: ~
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: crates/pixi_build_discovery/tests/discovery.rs
3+
expression: backend
4+
input_file: tests/data/discovery/ros-package/TEST-CASE
5+
---
6+
backend-spec:
7+
type: json-rpc
8+
name: pixi-build-ros
9+
command:
10+
type: environment-spec
11+
requirement:
12+
- pixi-build-ros
13+
- "*"
14+
channels:
15+
- "https://conda.anaconda.org/conda-forge"
16+
- "https://prefix.dev/pixi-build-backends"
17+
init-params:
18+
workspace-root: "file://<ROOT>/ros-package"
19+
source: ~
20+
source-anchor: "file://<ROOT>/ros-package"
21+
manifest-path: "file://<ROOT>/ros-package/package.xml"
22+
project-model:
23+
name: ~
24+
version: ~
25+
description: ~
26+
authors: ~
27+
license: ~
28+
licenseFile: ~
29+
readme: ~
30+
homepage: ~
31+
repository: ~
32+
documentation: ~
33+
targets: ~
34+
configuration: ~
35+
target-configuration: ~

pixi.lock

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)