use std::{
borrow::Cow,
collections::HashMap,
fmt::{Display, Formatter},
sync::Arc,
};
use indicatif::{HumanBytes, MultiProgress, ProgressBar, ProgressStyle};
use rattler::install::Placement;
use rattler_build_recipe::stage1::{Dependency, Requirements};
use rattler_build_types::{PinArgs, PinError};
use rattler_conda_types::{
ChannelUrl, MatchSpec, NamelessMatchSpec, PackageName, PackageNameMatcher, PackageRecord,
Platform, RepoDataRecord, package::RunExportsJson,
};
use rattler_repodata_gateway::{Gateway, RunExportExtractorError, RunExportsReporter};
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as};
use thiserror::Error;
use crate::{
metadata::{BuildConfiguration, Output, build_reindexed_channels},
package_cache_reporter::PackageCacheReporter,
render::{
run_exports::{filter_inherited_run_exports, filter_run_exports},
solver::{install_packages, solve_environment},
},
tool_configuration::{self, Configuration},
};
use super::reporters::GatewayReporter;
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DependencyInfo {
Variant(VariantDependency),
PinSubpackage(PinSubpackageDependency),
PinCompatible(PinCompatibleDependency),
RunExport(RunExportDependency),
Source(SourceDependency),
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VariantDependency {
pub variant: String,
#[serde_as(as = "DisplayFromStr")]
pub spec: MatchSpec,
}
impl From<VariantDependency> for DependencyInfo {
fn from(value: VariantDependency) -> Self {
DependencyInfo::Variant(value)
}
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PinSubpackageDependency {
#[serde(rename = "pin_subpackage")]
pub name: String,
#[serde(flatten)]
pub args: PinArgs,
#[serde_as(as = "DisplayFromStr")]
pub spec: MatchSpec,
}
impl From<PinSubpackageDependency> for DependencyInfo {
fn from(value: PinSubpackageDependency) -> Self {
DependencyInfo::PinSubpackage(value)
}
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PinCompatibleDependency {
#[serde(rename = "pin_compatible")]
pub name: String,
#[serde(flatten)]
pub args: PinArgs,
#[serde_as(as = "DisplayFromStr")]
pub spec: MatchSpec,
}
impl From<PinCompatibleDependency> for DependencyInfo {
fn from(value: PinCompatibleDependency) -> Self {
DependencyInfo::PinCompatible(value)
}
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RunExportDependency {
#[serde_as(as = "DisplayFromStr")]
pub spec: MatchSpec,
pub from: String,
#[serde(rename = "run_export")]
pub source_package: String,
}
impl From<RunExportDependency> for DependencyInfo {
fn from(value: RunExportDependency) -> Self {
DependencyInfo::RunExport(value)
}
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SourceDependency {
#[serde(rename = "source")]
#[serde_as(as = "DisplayFromStr")]
pub spec: MatchSpec,
}
impl From<SourceDependency> for DependencyInfo {
fn from(value: SourceDependency) -> Self {
DependencyInfo::Source(value)
}
}
impl DependencyInfo {
pub fn spec(&self) -> &MatchSpec {
match self {
DependencyInfo::Variant(spec) => &spec.spec,
DependencyInfo::PinSubpackage(spec) => &spec.spec,
DependencyInfo::PinCompatible(spec) => &spec.spec,
DependencyInfo::RunExport(spec) => &spec.spec,
DependencyInfo::Source(spec) => &spec.spec,
}
}
pub fn render(&self, long: bool) -> String {
if !long {
match self {
DependencyInfo::Variant(spec) => format!("{} (V)", &spec.spec),
DependencyInfo::PinSubpackage(spec) => format!("{} (PS)", &spec.spec),
DependencyInfo::PinCompatible(spec) => format!("{} (PC)", &spec.spec),
DependencyInfo::RunExport(spec) => format!(
"{} (RE of [{}: {}])",
&spec.spec, &spec.from, &spec.source_package
),
DependencyInfo::Source(spec) => spec.spec.to_string(),
}
} else {
match self {
DependencyInfo::Variant(spec) => format!("{} (from variant config)", &spec.spec),
DependencyInfo::PinSubpackage(spec) => {
format!("{} (from pin subpackage)", &spec.spec)
}
DependencyInfo::PinCompatible(spec) => {
format!("{} (from pin compatible)", &spec.spec)
}
DependencyInfo::RunExport(spec) => format!(
"{} (run export by {} in {} env)",
&spec.spec, &spec.from, &spec.source_package
),
DependencyInfo::Source(spec) => spec.spec.to_string(),
}
}
}
pub fn as_variant(&self) -> Option<&VariantDependency> {
match self {
DependencyInfo::Variant(spec) => Some(spec),
_ => None,
}
}
pub fn as_source(&self) -> Option<&SourceDependency> {
match self {
DependencyInfo::Source(spec) => Some(spec),
_ => None,
}
}
pub fn as_run_export(&self) -> Option<&RunExportDependency> {
match self {
DependencyInfo::RunExport(spec) => Some(spec),
_ => None,
}
}
pub fn as_pin_subpackage(&self) -> Option<&PinSubpackageDependency> {
match self {
DependencyInfo::PinSubpackage(spec) => Some(spec),
_ => None,
}
}
pub fn as_pin_compatible(&self) -> Option<&PinCompatibleDependency> {
match self {
DependencyInfo::PinCompatible(spec) => Some(spec),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FinalizedRunDependencies {
#[serde(default)]
pub depends: Vec<DependencyInfo>,
#[serde(default)]
pub constraints: Vec<DependencyInfo>,
#[serde(default, skip_serializing_if = "RunExportsJson::is_empty")]
pub run_exports: RunExportsJson,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedDependencies {
pub specs: Vec<DependencyInfo>,
pub resolved: Vec<RepoDataRecord>,
}
fn short_channel(channel: Option<&str>) -> String {
let channel = channel.unwrap_or_default();
if channel.contains('/') {
channel
.rsplit('/')
.find(|s| !s.is_empty())
.unwrap_or_default()
.to_string()
} else {
channel.to_string()
}
}
impl ResolvedDependencies {
pub fn to_table(&self, table: comfy_table::Table, long: bool) -> comfy_table::Table {
let mut table = table;
table.set_header(vec![
"Package", "Spec", "Version", "Build", "Channel", "Size",
]);
let column = table.column_mut(5).expect("This should be column two");
column.set_cell_alignment(comfy_table::CellAlignment::Right);
let resolved_w_specs = self
.resolved
.iter()
.map(|r| {
let spec = self
.specs
.iter()
.find(|s| s.spec().name.as_exact() == Some(&r.package_record.name));
if let Some(s) = spec {
(r, Some(s))
} else {
(r, None)
}
})
.collect::<Vec<_>>();
let (mut explicit, mut transient): (Vec<_>, Vec<_>) =
resolved_w_specs.into_iter().partition(|(_, s)| s.is_some());
explicit.sort_by(|(a, _), (b, _)| a.package_record.name.cmp(&b.package_record.name));
transient.sort_by(|(a, _), (b, _)| a.package_record.name.cmp(&b.package_record.name));
for (record, dep_info) in &explicit {
table.add_row([
record.package_record.name.as_normalized().to_string(),
dep_info
.expect("partition contains only values with Some")
.render(long),
record.package_record.version.to_string(),
record.package_record.build.to_string(),
short_channel(record.channel.as_deref()),
record
.package_record
.size
.map(|s| HumanBytes(s).to_string())
.unwrap_or_default(),
]);
}
for (record, _) in &transient {
table.add_row([
record.package_record.name.as_normalized().to_string(),
"".to_string(),
record.package_record.version.to_string(),
record.package_record.build.to_string(),
short_channel(record.channel.as_deref()),
record
.package_record
.size
.map(|s| HumanBytes(s).to_string())
.unwrap_or_default(),
]);
}
table
}
fn run_exports(&self, direct_only: bool) -> HashMap<PackageName, RunExportsJson> {
let mut result = HashMap::new();
for record in &self.resolved {
let Some(run_exports) = &record.package_record.run_exports else {
continue;
};
if direct_only
&& !self
.specs
.iter()
.filter(|s| !matches!(s, DependencyInfo::RunExport(_)))
.any(|s| s.spec().name.as_exact() == Some(&record.package_record.name))
{
continue;
}
result.insert(record.package_record.name.clone(), run_exports.clone());
}
result
}
}
impl Display for ResolvedDependencies {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut table = comfy_table::Table::new();
table
.load_preset(comfy_table::presets::UTF8_FULL_CONDENSED)
.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS);
write!(f, "{}", self.to_table(table, false))
}
}
fn render_grouped_dependencies(deps: &[DependencyInfo], long: bool) -> Vec<(String, String)> {
let mut items: Vec<(String, String)> = deps
.iter()
.map(|d| {
let rendered = d.render(long);
if let Some((name, rest)) = rendered.split_once(' ') {
(name.to_string(), rest.to_string())
} else {
(rendered.clone(), String::new())
}
})
.collect();
for (_, rest) in &mut items {
if rest.is_empty() {
*rest = "*".to_string();
}
}
items.sort_by(|a, b| a.0.cmp(&b.0));
items
}
impl FinalizedRunDependencies {
pub fn to_table(&self, table: comfy_table::Table, long: bool) -> comfy_table::Table {
let mut table = table;
table
.set_content_arrangement(comfy_table::ContentArrangement::Dynamic)
.set_header(vec!["Name", "Spec"]);
fn add_section_header(table: &mut comfy_table::Table, section_name: &str) {
let mut row = comfy_table::Row::new();
row.add_cell(
comfy_table::Cell::new(section_name).add_attribute(comfy_table::Attribute::Bold),
);
table.add_row(row);
}
fn add_grouped_items(table: &mut comfy_table::Table, items: &[(String, String)]) {
let mut prev_name: Option<&str> = None;
for (name, rest) in items {
let display_name = if prev_name == Some(name.as_str()) {
""
} else {
prev_name = Some(name.as_str());
name.as_str()
};
table.add_row(vec![display_name, rest.as_str()]);
}
}
fn add_simple_items(table: &mut comfy_table::Table, items: &[String]) {
for item in items {
table.add_row(item.splitn(2, ' ').collect::<Vec<&str>>());
}
}
let mut has_previous_section = false;
let depends_rendered = render_grouped_dependencies(&self.depends, long);
if !depends_rendered.is_empty() {
add_section_header(&mut table, "Run dependencies");
add_grouped_items(&mut table, &depends_rendered);
has_previous_section = true;
}
let constraints_rendered = render_grouped_dependencies(&self.constraints, long);
if !constraints_rendered.is_empty() {
if has_previous_section {
table.add_row(vec!["", ""]);
}
add_section_header(&mut table, "Run constraints");
add_grouped_items(&mut table, &constraints_rendered);
has_previous_section = true;
}
if !self.run_exports.is_empty() {
let sections = [
("Weak", &self.run_exports.weak),
("Strong", &self.run_exports.strong),
("Noarch", &self.run_exports.noarch),
("Weak constrains", &self.run_exports.weak_constrains),
("Strong constrains", &self.run_exports.strong_constrains),
];
for (name, exports) in sections {
if !exports.is_empty() {
if has_previous_section {
table.add_row(vec!["", ""]);
}
add_section_header(&mut table, &format!("Run exports ({name})"));
add_simple_items(&mut table, exports);
has_previous_section = true;
}
}
}
table
}
}
impl Display for FinalizedRunDependencies {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut table = comfy_table::Table::new();
table
.load_preset(comfy_table::presets::UTF8_FULL_CONDENSED)
.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS);
write!(f, "{}", self.to_table(table, false))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FinalizedDependencies {
pub build: Option<ResolvedDependencies>,
pub host: Option<ResolvedDependencies>,
pub run: FinalizedRunDependencies,
}
#[derive(Error, Debug)]
pub enum ResolveError {
#[error("Failed to get finalized dependencies")]
FinalizedDependencyNotFound,
#[error("Failed to resolve dependencies: {0}")]
DependencyResolutionError(String),
#[error("Could not collect run exports")]
CouldNotCollectRunExports(#[from] RunExportExtractorError),
#[error("Could not parse match spec: {0}")]
MatchSpecParseError(#[from] rattler_conda_types::ParseMatchSpecError),
#[error("Could not parse version spec for variant key {0}: {1}")]
VariantSpecParseError(String, rattler_conda_types::ParseMatchSpecError),
#[error("Could not apply pin: {0}")]
PinApplyError(#[from] PinError),
#[error("Could not apply pin_subpackage. The following subpackage is not available: {}", .0.as_normalized())]
PinSubpackageNotFound(PackageName),
#[error("Could not apply pin_compatible. The following package is not part of the solution: {}", .0.as_normalized())]
PinCompatibleNotFound(PackageName),
#[error("Compiler configuration error: {0}")]
CompilerError(String),
#[error("Could not reindex channels: {0}")]
RefreshChannelError(std::io::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunExportsDownload {
DownloadMissing,
SkipDownload,
}
pub fn apply_variant(
raw_specs: &[Dependency],
build_configuration: &BuildConfiguration,
compatibility_specs: &HashMap<PackageName, PackageRecord>,
build_time: bool,
) -> Result<Vec<DependencyInfo>, ResolveError> {
let variant = &build_configuration.variant;
let subpackages = &build_configuration.subpackages;
raw_specs
.iter()
.map(|s| {
match s {
Dependency::Spec(m) => {
let m = m.clone();
if build_time
&& m.version.is_none()
&& m.build.is_none()
&& let Some(name) = m.name.as_exact()
&& let Some(version) = variant.get(&name.as_normalized().to_string().into())
{
let mut spec = version.to_string();
if spec.chars().all(|c| c.is_alphanumeric() || c == '.') {
spec = format!("={spec}");
}
let variant = name.as_normalized().to_string();
let spec: NamelessMatchSpec = spec
.parse()
.map_err(|e| ResolveError::VariantSpecParseError(variant.clone(), e))?;
let spec =
MatchSpec::from_nameless(spec, PackageNameMatcher::Exact(name.clone()));
return Ok(VariantDependency { spec, variant }.into());
}
Ok(SourceDependency { spec: *m }.into())
}
Dependency::PinSubpackage(pin) => {
let name = &pin.pin_subpackage.name;
let subpackage = subpackages
.get(name)
.ok_or(ResolveError::PinSubpackageNotFound(name.clone()))?;
let pinned = pin
.pin_subpackage
.apply(&subpackage.version, &subpackage.build_string)?;
Ok(PinSubpackageDependency {
spec: pinned,
name: name.as_normalized().to_string(),
args: pin.pin_subpackage.args.clone(),
}
.into())
}
Dependency::PinCompatible(pin) => {
let name = &pin.pin_compatible.name;
let pin_package = compatibility_specs
.get(name)
.ok_or(ResolveError::PinCompatibleNotFound(name.clone()))?;
let pinned = pin
.pin_compatible
.apply(&pin_package.version, &pin_package.build)?;
Ok(PinCompatibleDependency {
spec: pinned,
name: name.as_normalized().to_string(),
args: pin.pin_compatible.args.clone(),
}
.into())
}
}
})
.collect()
}
use rattler::package_cache::CacheReporter;
use rattler_repodata_gateway::DownloadReporter;
struct RunExportsProgressReporter {
repodata_reporter: GatewayReporter,
package_cache_reporter: PackageCacheReporter,
}
impl RunExportsProgressReporter {
fn new(
repodata_reporter: GatewayReporter,
package_cache_reporter: PackageCacheReporter,
) -> Self {
Self {
repodata_reporter,
package_cache_reporter,
}
}
}
impl RunExportsReporter for RunExportsProgressReporter {
fn download_reporter(&self) -> Option<&dyn DownloadReporter> {
Some(&self.repodata_reporter)
}
fn create_package_download_reporter(
&self,
repo_data_record: &RepoDataRecord,
) -> Option<Box<dyn CacheReporter>> {
let mut reporter = self.package_cache_reporter.clone();
let entry = reporter.add(repo_data_record);
Some(Box::new(entry) as Box<dyn CacheReporter>)
}
}
async fn ensure_run_exports(
records: &mut [RepoDataRecord],
gateway: &Gateway,
multi_progress: MultiProgress,
progress_prefix: impl Into<Cow<'static, str>>,
top_level_pb: Option<ProgressBar>,
progress_style: ProgressStyle,
finish_style: ProgressStyle,
) -> Result<(), RunExportExtractorError> {
let progress_prefix: Cow<'static, str> = progress_prefix.into();
let placement = top_level_pb
.as_ref()
.map(|pb| Placement::After(pb.clone()))
.unwrap_or(Placement::End);
let repodata_reporter = GatewayReporter::builder()
.with_multi_progress(multi_progress.clone())
.with_progress_template(progress_style.clone())
.with_finish_template(finish_style.clone())
.with_placement(placement.clone())
.finish();
let package_cache_reporter =
PackageCacheReporter::new(multi_progress, placement).with_prefix(progress_prefix);
let reporter: Arc<dyn RunExportsReporter> = Arc::new(RunExportsProgressReporter::new(
repodata_reporter,
package_cache_reporter,
));
gateway
.ensure_run_exports(records.iter_mut(), Some(reporter))
.await
}
pub async fn install_environments(
output: &Output,
dependencies: &FinalizedDependencies,
tool_configuration: &tool_configuration::Configuration,
) -> Result<(), ResolveError> {
const EMPTY_RECORDS: Vec<RepoDataRecord> = Vec::new();
install_packages(
"build",
dependencies
.build
.as_ref()
.map(|deps| &deps.resolved)
.unwrap_or(&EMPTY_RECORDS),
output.build_configuration.build_platform.platform,
&output.build_configuration.directories.build_prefix,
tool_configuration,
)
.await
.map_err(|e| ResolveError::DependencyResolutionError(e.to_string()))?;
install_packages(
"host",
dependencies
.host
.as_ref()
.map(|deps| &deps.resolved)
.unwrap_or(&EMPTY_RECORDS),
output.build_configuration.host_platform.platform,
&output.build_configuration.directories.host_prefix,
tool_configuration,
)
.await
.map_err(|e| ResolveError::DependencyResolutionError(e.to_string()))?;
Ok(())
}
fn render_run_exports(
output: &Output,
compatibility_specs: &HashMap<PackageName, PackageRecord>,
) -> Result<RunExportsJson, ResolveError> {
let render_run_exports = |run_export: &[Dependency]| -> Result<Vec<String>, ResolveError> {
let rendered = apply_variant(
run_export,
&output.build_configuration,
compatibility_specs,
false,
)?;
Ok(rendered
.iter()
.map(|dep| dep.spec().to_string())
.collect::<Vec<_>>())
};
let run_exports = &output.recipe.requirements().run_exports;
if !run_exports.is_empty() {
Ok(RunExportsJson {
strong: render_run_exports(&run_exports.strong)?,
weak: render_run_exports(&run_exports.weak)?,
noarch: render_run_exports(&run_exports.noarch)?,
strong_constrains: render_run_exports(&run_exports.strong_constraints)?,
weak_constrains: render_run_exports(&run_exports.weak_constraints)?,
})
} else {
Ok(RunExportsJson::default())
}
}
pub(crate) async fn resolve_dependencies(
requirements: &Requirements,
output: &Output,
channels: &[ChannelUrl],
tool_configuration: &tool_configuration::Configuration,
download_missing_run_exports: RunExportsDownload,
) -> Result<FinalizedDependencies, ResolveError> {
let merge_build_host = output.recipe.build().merge_build_and_host_envs;
let mut compatibility_specs = HashMap::new();
let gateway = if download_missing_run_exports == RunExportsDownload::DownloadMissing {
let client = tool_configuration.client.get_client().clone();
let package_cache = tool_configuration.package_cache.clone();
Some(
Gateway::builder()
.with_max_concurrent_requests(50)
.with_client(client)
.with_package_cache(package_cache)
.finish(),
)
} else {
None
};
let ignore_run_exports = if output.recipe.build.python.version_independent {
let mut ignore = requirements.ignore_run_exports.clone();
let python: PackageName = "python".parse().expect("valid package name");
ignore.from_package.push(python.clone());
ignore.by_name.push(python);
ignore
} else {
requirements.ignore_run_exports.clone()
};
let build_env = if !requirements.build.is_empty() && !merge_build_host {
let build_env_specs = apply_variant(
&requirements.build,
&output.build_configuration,
&compatibility_specs,
true,
)?;
let match_specs = build_env_specs
.iter()
.map(|s| s.spec().clone())
.collect::<Vec<_>>();
let mut resolved = solve_environment(
"build",
&match_specs,
&output.build_configuration.build_platform,
channels,
tool_configuration,
output.build_configuration.channel_priority,
output.build_configuration.solve_strategy,
output.build_configuration.exclude_newer,
)
.await
.map_err(|e| ResolveError::DependencyResolutionError(e.to_string()))?;
if download_missing_run_exports == RunExportsDownload::DownloadMissing {
tool_configuration
.fancy_log_handler
.wrap_in_progress_async_with_progress("Collecting run exports", |pb| {
let progress_style = tool_configuration.fancy_log_handler.default_bytes_style();
let finish_style = tool_configuration
.fancy_log_handler
.finished_progress_style();
ensure_run_exports(
&mut resolved,
gateway.as_ref().unwrap(),
tool_configuration
.fancy_log_handler
.multi_progress()
.clone(),
tool_configuration
.fancy_log_handler
.with_indent_levels(" "),
Some(pb),
progress_style,
finish_style,
)
})
.await
.map_err(ResolveError::CouldNotCollectRunExports)?;
}
resolved.iter().for_each(|r| {
compatibility_specs.insert(r.package_record.name.clone(), r.package_record.clone());
});
Some(ResolvedDependencies {
specs: build_env_specs,
resolved,
})
} else {
None
};
let mut host_env_specs = apply_variant(
&requirements.host,
&output.build_configuration,
&compatibility_specs,
true,
)?;
let mut build_run_exports = HashMap::new();
if let Some(build_env) = &build_env {
build_run_exports.extend(build_env.run_exports(true));
}
let build_run_exports = filter_run_exports(&ignore_run_exports, &build_run_exports, "build")?;
host_env_specs.extend(build_run_exports.strong.iter().cloned());
let mut match_specs = host_env_specs
.iter()
.map(|s| s.spec().clone())
.collect::<Vec<_>>();
if merge_build_host {
let specs = apply_variant(
&requirements.build,
&output.build_configuration,
&compatibility_specs,
true,
)?;
match_specs.extend(specs.iter().map(|s| s.spec().clone()));
}
let host_env = if !match_specs.is_empty() {
let mut resolved = solve_environment(
"host",
&match_specs,
&output.build_configuration.host_platform,
channels,
tool_configuration,
output.build_configuration.channel_priority,
output.build_configuration.solve_strategy,
output.build_configuration.exclude_newer,
)
.await
.map_err(|e| ResolveError::DependencyResolutionError(e.to_string()))?;
if download_missing_run_exports == RunExportsDownload::DownloadMissing {
tool_configuration
.fancy_log_handler
.wrap_in_progress_async_with_progress("Collecting run exports", |pb| {
let progress_style = tool_configuration.fancy_log_handler.default_bytes_style();
let finish_style = tool_configuration
.fancy_log_handler
.finished_progress_style();
ensure_run_exports(
&mut resolved,
gateway.as_ref().unwrap(),
tool_configuration
.fancy_log_handler
.multi_progress()
.clone(),
tool_configuration
.fancy_log_handler
.with_indent_levels(" "),
Some(pb),
progress_style,
finish_style,
)
})
.await
.map_err(ResolveError::CouldNotCollectRunExports)?;
}
resolved.iter().for_each(|r| {
compatibility_specs.insert(r.package_record.name.clone(), r.package_record.clone());
});
Some(ResolvedDependencies {
specs: host_env_specs,
resolved,
})
} else {
None
};
let mut depends = apply_variant(
&requirements.run,
&output.build_configuration,
&compatibility_specs,
false,
)?;
let mut constraints = apply_variant(
&requirements.run_constraints,
&output.build_configuration,
&compatibility_specs,
false,
)?;
if let Some(finalized_cache) = &output.finalized_cache_dependencies {
let should_inherit_run_exports = output
.recipe
.inherits_from
.as_ref()
.map(|i| i.inherit_run_exports)
.unwrap_or(true);
let inherited_deps: Vec<_> = if should_inherit_run_exports {
filter_inherited_run_exports(&ignore_run_exports, &finalized_cache.run.depends)
} else {
finalized_cache
.run
.depends
.iter()
.filter(|dep| !matches!(dep, DependencyInfo::RunExport(_)))
.cloned()
.collect()
};
depends.extend(inherited_deps);
constraints = constraints
.iter()
.chain(finalized_cache.run.constraints.iter())
.filter(|c| !matches!(c, DependencyInfo::RunExport(_)))
.cloned()
.collect();
}
let rendered_run_exports = render_run_exports(output, &compatibility_specs)?;
let mut host_run_exports = HashMap::new();
if let Some(host_env) = &host_env {
host_run_exports.extend(host_env.run_exports(true));
}
let host_run_exports = filter_run_exports(&ignore_run_exports, &host_run_exports, "host")?;
if output.target_platform() == &Platform::NoArch {
depends.extend(host_run_exports.noarch.iter().cloned());
} else {
depends.extend(build_run_exports.strong.iter().cloned());
depends.extend(host_run_exports.strong.iter().cloned());
depends.extend(host_run_exports.weak.iter().cloned());
constraints.extend(build_run_exports.strong_constraints.iter().cloned());
constraints.extend(host_run_exports.strong_constraints.iter().cloned());
constraints.extend(host_run_exports.weak_constraints.iter().cloned());
}
let run_specs = FinalizedRunDependencies {
depends,
constraints,
run_exports: rendered_run_exports,
};
if run_specs.depends.is_empty() && run_specs.constraints.is_empty() {
tracing::info!("\nFinalized run dependencies: this output has no run dependencies");
} else {
tracing::info!(
"\nFinalized run dependencies ({}):\n{}",
output.identifier(),
run_specs
);
}
Ok(FinalizedDependencies {
build: build_env,
host: host_env,
run: run_specs,
})
}
impl Output {
pub async fn resolve_dependencies(
self,
tool_configuration: &tool_configuration::Configuration,
download_missing_run_exports: RunExportsDownload,
) -> Result<Output, ResolveError> {
if self.finalized_dependencies.is_some() {
return Ok(self);
}
let channels = build_reindexed_channels(&self.build_configuration, tool_configuration)
.await
.map_err(ResolveError::RefreshChannelError)?;
let finalized_dependencies = resolve_dependencies(
self.recipe.requirements(),
&self,
&channels,
tool_configuration,
download_missing_run_exports,
)
.await?;
Ok(Output {
finalized_dependencies: Some(finalized_dependencies),
..self.clone()
})
}
pub async fn install_environments(
&self,
tool_configuration: &Configuration,
) -> Result<(), ResolveError> {
let dependencies = self
.finalized_dependencies
.as_ref()
.ok_or(ResolveError::FinalizedDependencyNotFound)?;
if tool_configuration.environments_externally_managed {
let span = tracing::info_span!(
"Externally resolved dependencies",
recipe = self.identifier()
);
let _enter = span.enter();
if let Some(build) = &dependencies.build {
tracing::info!(
"\nResolved build dependencies({}):\n{}",
self.identifier(),
build
);
}
if let Some(host) = &dependencies.host {
tracing::info!(
"Resolved host dependencies({}):\n{}",
self.identifier(),
host
);
}
tracing::info!(
"Resolved run dependencies({}):\n{}",
self.identifier(),
dependencies.run
);
return Ok(());
}
install_environments(self, dependencies, tool_configuration).await
}
}
#[cfg(test)]
mod tests {
use rattler_conda_types::ParseStrictness;
use super::*;
#[test]
fn test_dependency_info_render() {
let dep_info: Vec<DependencyInfo> = vec![
SourceDependency {
spec: MatchSpec::from_str("xyz", ParseStrictness::Strict).unwrap(),
}
.into(),
VariantDependency {
spec: MatchSpec::from_str("foo", ParseStrictness::Strict).unwrap(),
variant: "bar".to_string(),
}
.into(),
PinSubpackageDependency {
name: "baz".to_string(),
spec: MatchSpec::from_str("baz", ParseStrictness::Strict).unwrap(),
args: PinArgs {
upper_bound: Some("x.x".parse().unwrap()),
lower_bound: Some("x.x.x".parse().unwrap()),
exact: true,
..Default::default()
},
}
.into(),
PinCompatibleDependency {
name: "bat".to_string(),
spec: MatchSpec::from_str("bat", ParseStrictness::Strict).unwrap(),
args: PinArgs {
upper_bound: Some("x.x".parse().unwrap()),
lower_bound: Some("x.x.x".parse().unwrap()),
exact: true,
..Default::default()
},
}
.into(),
];
let yaml_str = serde_yaml::to_string(&dep_info).unwrap();
insta::assert_snapshot!(yaml_str);
let dep_info: Vec<DependencyInfo> = serde_yaml::from_str(&yaml_str).unwrap();
assert_eq!(dep_info.len(), 4);
assert!(matches!(dep_info[0], DependencyInfo::Source(_)));
assert!(matches!(dep_info[1], DependencyInfo::Variant(_)));
assert!(matches!(dep_info[2], DependencyInfo::PinSubpackage(_)));
assert!(matches!(dep_info[3], DependencyInfo::PinCompatible(_)));
}
}