Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: variants in metadata #57

Merged
merged 2 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 178 additions & 110 deletions crates/pixi-build/src/bin/pixi-build-cmake/cmake.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
borrow::Cow,
collections::BTreeMap,
collections::{BTreeMap, HashMap},
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
Expand All @@ -17,7 +17,9 @@ use pixi_build_backend::{
};
use pixi_build_types::{
procedures::{
conda_build::{CondaBuildParams, CondaBuildResult, CondaBuiltPackage},
conda_build::{
CondaBuildParams, CondaBuildResult, CondaBuiltPackage, CondaOutputIdentifier,
},
conda_metadata::{CondaMetadataParams, CondaMetadataResult},
initialize::{InitializeParams, InitializeResult},
negotiate_capabilities::{NegotiateCapabilitiesParams, NegotiateCapabilitiesResult},
Expand Down Expand Up @@ -290,32 +292,16 @@ impl CMakeBuildBackend {
}

/// Returns the build configuration for a recipe
pub async fn build_configuration(
pub fn build_configuration(
&self,
recipe: &Recipe,
channels: Vec<Url>,
build_platform: Option<PlatformAndVirtualPackages>,
host_platform: Option<PlatformAndVirtualPackages>,
work_directory: &Path,
variant: BTreeMap<NormalizedKey, String>,
directories: Directories,
) -> miette::Result<BuildConfiguration> {
// Parse the package name from the manifest
let name = self.package_manifest.package.name.clone();
let name = PackageName::from_str(&name).into_diagnostic()?;
// TODO: Setup defaults
std::fs::create_dir_all(work_directory)
.into_diagnostic()
.context("failed to create output directory")?;
let directories = Directories::setup(
name.as_normalized(),
&self.manifest_path,
work_directory,
true,
&Utc::now(),
)
.into_diagnostic()
.context("failed to setup build directories")?;

let build_platform = build_platform.map(|p| PlatformWithVirtualPackages {
platform: p.platform,
virtual_packages: p.virtual_packages.unwrap_or_default(),
Expand Down Expand Up @@ -362,6 +348,38 @@ impl CMakeBuildBackend {
sandbox_config: None,
})
}

pub fn compute_variants(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use some docs

&self,
input_variant_configuration: Option<HashMap<String, Vec<String>>>,
host_platform: Platform,
) -> miette::Result<Vec<BTreeMap<NormalizedKey, String>>> {
// Create a variant config from the variant configuration in the parameters.
let variant_config = VariantConfig {
variants: input_variant_configuration
.unwrap_or_default()
.into_iter()
.map(|(key, values)| (key.into(), values))
.collect(),
pin_run_as_build: None,
zip_keys: None,
};

// Determine the variant keys that are used in the recipe.
let used_variants = self
.package_manifest
.targets
.resolve(Some(host_platform))
.flat_map(|dep| dep.dependencies.values().flatten())
.filter(|(_, spec)| can_be_used_as_variant(spec))
.map(|(name, _)| name.into())
.collect();

// Determine the combinations of the used variants.
variant_config
.combinations(&used_variants, None)
.into_diagnostic()
}
}

fn input_globs() -> Vec<String> {
Expand Down Expand Up @@ -406,49 +424,38 @@ impl Protocol for CMakeBuildBackend {
.finish(),
);

// Create a variant config from the variant configuration in the parameters.
let variant_config = VariantConfig {
variants: params
.variant_configuration
.unwrap_or_default()
.into_iter()
.map(|(key, values)| (key.into(), values))
.collect(),
pin_run_as_build: None,
zip_keys: None,
};
let package_name = PackageName::from_str(&self.package_manifest.package.name)
.into_diagnostic()
.context("`{name}` is not a valid package name")?;

// Determine the variant keys that are used in the recipe.
let used_variants = self
.package_manifest
.targets
.resolve(Some(host_platform))
.flat_map(|dep| dep.dependencies.values().flatten())
.filter(|(_, spec)| can_be_used_as_variant(spec))
.map(|(name, _)| name.into())
.collect();
let directories = Directories::setup(
package_name.as_normalized(),
&self.manifest_path,
&params.work_directory,
true,
&Utc::now(),
)
.into_diagnostic()
.context("failed to setup build directories")?;

// Determine the combinations of the used variants.
let combinations = variant_config
.combinations(&used_variants, None)
.into_diagnostic()?;
// Create a variant config from the variant configuration in the parameters.
let variant_combinations =
self.compute_variants(params.variant_configuration, host_platform)?;

// Construct the different outputs
let mut packages = Vec::new();
for variant in combinations {
for variant in variant_combinations {
// TODO: Determine how and if we can determine this from the manifest.
let recipe = self.recipe(host_platform, &channel_config, &variant)?;
let output = Output {
build_configuration: self
.build_configuration(
&recipe,
channels.clone(),
params.build_platform.clone(),
params.host_platform.clone(),
&params.work_directory,
variant,
)
.await?,
build_configuration: self.build_configuration(
&recipe,
channels.clone(),
params.build_platform.clone(),
params.host_platform.clone(),
variant,
directories.clone(),
)?,
recipe,
finalized_dependencies: None,
finalized_cache_dependencies: None,
Expand Down Expand Up @@ -529,74 +536,135 @@ impl Protocol for CMakeBuildBackend {
.map(|p| p.platform)
.unwrap_or_else(Platform::current);

let variant = BTreeMap::new();

let recipe = self.recipe(host_platform, &channel_config, &variant)?;
let output = Output {
build_configuration: self
.build_configuration(
&recipe,
channels,
params.host_platform.clone(),
Some(PlatformAndVirtualPackages {
platform: host_platform,
virtual_packages: params.build_platform_virtual_packages,
}),
&params.work_directory,
variant,
)
.await?,
recipe,
finalized_dependencies: None,
finalized_cache_dependencies: None,
finalized_cache_sources: None,
finalized_sources: None,
build_summary: Arc::default(),
system_tools: Default::default(),
extra_meta: None,
};
let tool_config = Configuration::builder()
.with_opt_cache_dir(self.cache_dir.clone())
.with_logging_output_handler(self.logging_output_handler.clone())
.with_channel_config(channel_config.clone())
.with_testing(false)
.with_keep_build(true)
.finish();
let package_name = PackageName::from_str(&self.package_manifest.package.name)
.into_diagnostic()
.context("`{name}` is not a valid package name")?;

let temp_recipe = TemporaryRenderedRecipe::from_output(&output)?;
let mut output_with_build_string = output.clone();
let directories = Directories::setup(
package_name.as_normalized(),
&self.manifest_path,
&params.work_directory,
true,
&Utc::now(),
)
.into_diagnostic()
.context("failed to setup build directories")?;

let selector_config = output.build_configuration.selector_config();
// Recompute all the variant combinations
let variant_combinations =
self.compute_variants(params.variant_configuration, host_platform)?;

let jinja = Jinja::new(selector_config.clone()).with_context(&output.recipe.context);
// Compute outputs for each variant
let mut outputs = Vec::with_capacity(variant_combinations.len());
for variant in variant_combinations {
let recipe = self.recipe(host_platform, &channel_config, &variant)?;
let build_configuration = self.build_configuration(
&recipe,
channels.clone(),
params.host_platform.clone(),
Some(PlatformAndVirtualPackages {
platform: host_platform,
virtual_packages: params.build_platform_virtual_packages.clone(),
}),
variant,
directories.clone(),
)?;

let mut output = Output {
build_configuration,
recipe,
finalized_dependencies: None,
finalized_cache_dependencies: None,
finalized_cache_sources: None,
finalized_sources: None,
build_summary: Arc::default(),
system_tools: Default::default(),
extra_meta: None,
};

let hash = HashInfo::from_variant(output.variant(), output.recipe.build().noarch());
let build_string =
output
// Resolve the build string
let selector_config = output.build_configuration.selector_config();
let jinja = Jinja::new(selector_config.clone()).with_context(&output.recipe.context);
let hash = HashInfo::from_variant(output.variant(), output.recipe.build().noarch());
let build_string = output
.recipe
.build()
.string()
.resolve(&hash, output.recipe.build().number(), &jinja);
output_with_build_string.recipe.build.string =
BuildString::Resolved(build_string.to_string());

let tool_config = Arc::new(tool_config);
let (output, package) = temp_recipe
.within_context_async(move || async move {
run_build(output_with_build_string, &tool_config).await
})
.await?;

Ok(CondaBuildResult {
packages: vec![CondaBuiltPackage {
.resolve(&hash, output.recipe.build().number(), &jinja)
.into_owned();
output.recipe.build.string = BuildString::Resolved(build_string);

outputs.push(output);
}

// Setup tool configuration
let tool_config = Arc::new(
Configuration::builder()
.with_opt_cache_dir(self.cache_dir.clone())
.with_logging_output_handler(self.logging_output_handler.clone())
.with_channel_config(channel_config.clone())
.with_testing(false)
.with_keep_build(true)
.finish(),
);

// Determine the outputs to build
let selected_outputs = if let Some(output_identifiers) = params.outputs {
output_identifiers
.into_iter()
.filter_map(|iden| {
let pos = outputs.iter().position(|output| {
let CondaOutputIdentifier {
name,
version,
build,
subdir,
} = &iden;
name.as_ref()
.map_or(true, |n| output.name().as_normalized() == n)
&& version
.as_ref()
.map_or(true, |v| output.version().to_string() == *v)
&& build
.as_ref()
.map_or(true, |b| output.build_string() == b.as_str())
&& subdir
.as_ref()
.map_or(true, |s| output.target_platform().as_str() == s)
})?;
Some(outputs.remove(pos))
})
.collect()
} else {
outputs
};

let mut packages = Vec::with_capacity(selected_outputs.len());
for output in selected_outputs {
let temp_recipe = TemporaryRenderedRecipe::from_output(&output)?;
let build_string = output
.recipe
.build
.string
.as_resolved()
.expect("build string must have already been resolved")
.to_string();
let tool_config = tool_config.clone();
let (output, package) = temp_recipe
.within_context_async(move || async move { run_build(output, &tool_config).await })
.await?;
let built_package = CondaBuiltPackage {
output_file: package,
input_globs: input_globs(),
name: output.name().as_normalized().to_string(),
version: output.version().to_string(),
build: build_string.to_string(),
subdir: output.target_platform().to_string(),
}],
})
};
packages.push(built_package);
}

Ok(CondaBuildResult { packages })
}
}

Expand Down
Loading
Loading