Skip to content

Commit 1973dda

Browse files
committed
wip: support requesting all dependencies of an output
1 parent ddd6270 commit 1973dda

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,31 @@ impl CommandDispatcher {
485485
self.execute_task(spec).await
486486
}
487487

488+
/// Returns the dependencies (build, host, and run) for a specific output
489+
/// from a source package.
490+
///
491+
/// This method retrieves metadata from the build backend for the given
492+
/// source and extracts the dependencies for the specified output name.
493+
/// The dependencies are returned in their raw form as specified by the
494+
/// backend, including both regular dependencies and constraints.
495+
///
496+
/// This is useful when you want to install the dependencies of a source
497+
/// package without actually building it - for instance, to set up a
498+
/// development environment or to inspect what would be needed to build
499+
/// the package.
500+
///
501+
/// # Requirements
502+
///
503+
/// - The build backend must support the `conda/outputs` procedure (API v1+)
504+
/// - The specified output must exist in the source package
505+
pub async fn get_output_dependencies(
506+
&self,
507+
spec: crate::GetOutputDependenciesSpec,
508+
) -> Result<crate::OutputDependencies, CommandDispatcherError<crate::GetOutputDependenciesError>>
509+
{
510+
spec.request(self.clone()).await
511+
}
512+
488513
/// Calls into a pixi build backend to perform a source build.
489514
pub(crate) async fn backend_source_build(
490515
&self,

crates/pixi_command_dispatcher/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ mod executor;
4343
mod install_pixi;
4444
mod instantiate_tool_env;
4545
mod limits;
46+
mod output_dependencies;
4647
mod package_identifier;
4748
pub mod reporter;
4849
mod solve_conda;
@@ -72,6 +73,9 @@ pub use install_pixi::{
7273
};
7374
pub use instantiate_tool_env::{InstantiateToolEnvironmentError, InstantiateToolEnvironmentSpec};
7475
pub use limits::Limits;
76+
pub use output_dependencies::{
77+
GetOutputDependenciesError, GetOutputDependenciesSpec, OutputDependencies,
78+
};
7579
pub use package_identifier::PackageIdentifier;
7680
pub use reporter::{
7781
CondaSolveReporter, GitCheckoutReporter, PixiInstallReporter, PixiSolveReporter, Reporter,
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
use std::collections::BTreeMap;
2+
3+
use miette::Diagnostic;
4+
use pixi_build_discovery::EnabledProtocols;
5+
use pixi_build_types::procedures::conda_outputs::{CondaOutput, CondaOutputDependencies};
6+
use pixi_record::PinnedSourceSpec;
7+
use rattler_conda_types::{ChannelConfig, ChannelUrl, PackageName};
8+
use thiserror::Error;
9+
use tracing::instrument;
10+
11+
use crate::{
12+
BuildBackendMetadataError, BuildBackendMetadataSpec, BuildEnvironment, CommandDispatcher,
13+
CommandDispatcherError, CommandDispatcherErrorResultExt,
14+
build::source_metadata_cache::MetadataKind,
15+
};
16+
17+
/// A specification for retrieving the dependencies of a specific output from a
18+
/// source package.
19+
#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize)]
20+
pub struct GetOutputDependenciesSpec {
21+
/// The source specification. This should be a pinned source (e.g., a
22+
/// specific git commit) to ensure reproducibility.
23+
pub source: PinnedSourceSpec,
24+
25+
/// The name of the output to retrieve dependencies for.
26+
pub output_name: PackageName,
27+
28+
/// The channel configuration to use for the build backend.
29+
pub channel_config: ChannelConfig,
30+
31+
/// The channels to use for solving.
32+
#[serde(skip_serializing_if = "Vec::is_empty")]
33+
pub channels: Vec<ChannelUrl>,
34+
35+
/// Information about the build environment.
36+
pub build_environment: BuildEnvironment,
37+
38+
/// Variant configuration
39+
pub variants: Option<BTreeMap<String, Vec<String>>>,
40+
41+
/// The protocols that are enabled for this source
42+
#[serde(skip_serializing_if = "crate::is_default")]
43+
pub enabled_protocols: EnabledProtocols,
44+
}
45+
46+
/// The dependencies of a specific output from a source package.
47+
#[derive(Debug, Clone)]
48+
pub struct OutputDependencies {
49+
/// The build dependencies of the package. These refer to the packages that
50+
/// should be installed in the "build" environment.
51+
pub build_dependencies: Option<CondaOutputDependencies>,
52+
53+
/// The "host" dependencies of the package. These refer to the packages that
54+
/// should be installed to be able to refer to them from the build process
55+
/// but not run them.
56+
pub host_dependencies: Option<CondaOutputDependencies>,
57+
58+
/// The dependencies for the run environment of the package.
59+
pub run_dependencies: CondaOutputDependencies,
60+
}
61+
62+
impl GetOutputDependenciesSpec {
63+
#[instrument(
64+
skip_all,
65+
name = "output-dependencies",
66+
fields(
67+
source = %self.source,
68+
output = %self.output_name.as_source(),
69+
platform = %self.build_environment.host_platform,
70+
)
71+
)]
72+
pub(crate) async fn request(
73+
self,
74+
command_dispatcher: CommandDispatcher,
75+
) -> Result<OutputDependencies, CommandDispatcherError<GetOutputDependenciesError>> {
76+
// Get the metadata from the build backend.
77+
let backend_metadata_spec = BuildBackendMetadataSpec {
78+
source: self.source.clone(),
79+
channel_config: self.channel_config,
80+
channels: self.channels,
81+
build_environment: self.build_environment,
82+
variants: self.variants,
83+
enabled_protocols: self.enabled_protocols,
84+
};
85+
86+
let build_backend_metadata = command_dispatcher
87+
.build_backend_metadata(backend_metadata_spec)
88+
.await
89+
.map_err_with(GetOutputDependenciesError::BuildBackendMetadata)?;
90+
91+
// Extract the outputs from the metadata.
92+
let outputs = match &build_backend_metadata.metadata.metadata {
93+
MetadataKind::Outputs { outputs } => outputs,
94+
MetadataKind::GetMetadata { .. } => {
95+
return Err(CommandDispatcherError::Failed(
96+
GetOutputDependenciesError::UnsupportedProtocol,
97+
));
98+
}
99+
};
100+
101+
// Find the output with the matching name.
102+
let output = outputs
103+
.iter()
104+
.find(|output| output.metadata.name == self.output_name)
105+
.ok_or_else(|| {
106+
CommandDispatcherError::Failed(GetOutputDependenciesError::OutputNotFound {
107+
output_name: self.output_name.clone(),
108+
available_outputs: outputs.iter().map(|o| o.metadata.name.clone()).collect(),
109+
})
110+
})?;
111+
112+
// Extract and return the dependencies.
113+
Ok(extract_dependencies(output))
114+
}
115+
}
116+
117+
/// Extracts the dependencies from a CondaOutput.
118+
fn extract_dependencies(output: &CondaOutput) -> OutputDependencies {
119+
OutputDependencies {
120+
build_dependencies: output.build_dependencies.clone(),
121+
host_dependencies: output.host_dependencies.clone(),
122+
run_dependencies: output.run_dependencies.clone(),
123+
}
124+
}
125+
126+
#[derive(Debug, Error, Diagnostic)]
127+
pub enum GetOutputDependenciesError {
128+
#[error(transparent)]
129+
#[diagnostic(transparent)]
130+
BuildBackendMetadata(#[from] BuildBackendMetadataError),
131+
132+
#[error(
133+
"the build backend does not support the `conda/outputs` procedure, which is required to retrieve output-specific dependencies"
134+
)]
135+
UnsupportedProtocol,
136+
137+
#[error(
138+
"the output '{}' was not found in the source package. Available outputs: {}",
139+
output_name.as_source(),
140+
available_outputs.iter().map(|n| n.as_source()).collect::<Vec<_>>().join(", ")
141+
)]
142+
OutputNotFound {
143+
output_name: PackageName,
144+
available_outputs: Vec<PackageName>,
145+
},
146+
}

0 commit comments

Comments
 (0)