diff --git a/crates/pixi-build-backend/src/generated_recipe.rs b/crates/pixi-build-backend/src/generated_recipe.rs index 576dfd3f..5d3788cf 100644 --- a/crates/pixi-build-backend/src/generated_recipe.rs +++ b/crates/pixi-build-backend/src/generated_recipe.rs @@ -89,7 +89,7 @@ pub trait GenerateRecipe { } pub trait BackendConfig: DeserializeOwned + Clone { - /// At least debug dir should be provided by the backend config + /// Debug dir provided by the backend config fn debug_dir(&self) -> Option<&Path>; /// Merge this configuration with a target-specific configuration. diff --git a/crates/pixi-build-backend/src/intermediate_backend.rs b/crates/pixi-build-backend/src/intermediate_backend.rs index 55e9e7a6..19190248 100644 --- a/crates/pixi-build-backend/src/intermediate_backend.rs +++ b/crates/pixi-build-backend/src/intermediate_backend.rs @@ -65,6 +65,8 @@ use crate::{ utils::TemporaryRenderedRecipe, }; +use fs_err::tokio as tokio_fs; + #[derive(Debug, Default, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct IntermediateBackendConfig { @@ -415,6 +417,27 @@ where }, ); + let directories = output_directory( + if number_of_outputs == 1 { + OneOrMultipleOutputs::Single(discovered_output.name.clone()) + } else { + OneOrMultipleOutputs::OneOfMany(discovered_output.name.clone()) + }, + params.work_directory.clone(), + &named_source.path, + ); + // Save intermediate recipe in the debug dir + let debug_path = directories.work_dir.join("recipe.yaml"); + tokio_fs::create_dir_all(&directories.work_dir) + .await + .into_diagnostic()?; + tokio_fs::write( + &debug_path, + generated_recipe.recipe.to_yaml_pretty().into_diagnostic()?, + ) + .await + .into_diagnostic()?; + let mut output = Output { recipe, build_configuration: BuildConfiguration { @@ -764,6 +787,28 @@ where }, ); + let directories = output_directory( + if number_of_outputs == 1 { + OneOrMultipleOutputs::Single(discovered_output.name.clone()) + } else { + OneOrMultipleOutputs::OneOfMany(discovered_output.name.clone()) + }, + params.work_directory.clone(), + &named_source.path, + ); + + // Save intermediate recipe in the debug dir + let debug_path = directories.work_dir.join("recipe.yaml"); + tokio_fs::create_dir_all(&directories.work_dir) + .await + .into_diagnostic()?; + tokio_fs::write( + &debug_path, + generated_recipe.recipe.to_yaml_pretty().into_diagnostic()?, + ) + .await + .into_diagnostic()?; + let mut output = Output { recipe, build_configuration: BuildConfiguration { @@ -912,7 +957,7 @@ where variant_config.variants.append(&mut param_variants); // Construct the intermediate recipe - let recipe = self.generate_recipe.generate_recipe( + let generated_recipe = self.generate_recipe.generate_recipe( &self.project_model, &config, self.source_dir.clone(), @@ -928,7 +973,13 @@ where let recipe_path = self.source_dir.join(&self.manifest_rel_path); let named_source = Source { name: self.manifest_rel_path.display().to_string(), - code: Arc::from(recipe.recipe.to_yaml_pretty().into_diagnostic()?.as_str()), + code: Arc::from( + generated_recipe + .recipe + .to_yaml_pretty() + .into_diagnostic()? + .as_str(), + ), path: recipe_path.clone(), }; @@ -962,6 +1013,8 @@ where let mut subpackages = HashMap::new(); let mut outputs = Vec::new(); + + let num_of_outputs = discovered_outputs.len(); for discovered_output in discovered_outputs { let variant = discovered_output.used_vars; let hash = HashInfo::from_variant(&variant, &discovered_output.noarch_type); @@ -993,6 +1046,28 @@ where let build_number = recipe.build().number; + let directories = output_directory( + if num_of_outputs == 1 { + OneOrMultipleOutputs::Single(discovered_output.name.clone()) + } else { + OneOrMultipleOutputs::OneOfMany(discovered_output.name.clone()) + }, + params.work_directory.clone(), + &named_source.path, + ); + + // Save intermediate recipe in the debug dir + let debug_path = directories.work_dir.join("recipe.yaml"); + tokio_fs::create_dir_all(&directories.work_dir) + .await + .into_diagnostic()?; + tokio_fs::write( + &debug_path, + generated_recipe.recipe.to_yaml_pretty().into_diagnostic()?, + ) + .await + .into_diagnostic()?; + subpackages.insert( recipe.package().name().clone(), PackageIdentifier { @@ -1103,7 +1178,7 @@ where Ok(CondaOutputsResult { outputs, - input_globs: recipe.metadata_input_globs, + input_globs: generated_recipe.metadata_input_globs, }) } @@ -1194,6 +1269,18 @@ where recipe_path, ); + // Save intermediate recipe in the debug dir + let debug_path = directories.work_dir.join("recipe.yaml"); + tokio_fs::create_dir_all(&directories.work_dir) + .await + .into_diagnostic()?; + tokio_fs::write( + &debug_path, + recipe.recipe.to_yaml_pretty().into_diagnostic()?, + ) + .await + .into_diagnostic()?; + let tool_config = Configuration::builder() .with_opt_cache_dir(self.cache_dir.clone()) .with_logging_output_handler(self.logging_output_handler.clone()) diff --git a/crates/pixi-build-backend/tests/fixtures/default_minimal_project_model_for_build.json b/crates/pixi-build-backend/tests/fixtures/default_minimal_project_model_for_build.json new file mode 100644 index 00000000..d125e55c --- /dev/null +++ b/crates/pixi-build-backend/tests/fixtures/default_minimal_project_model_for_build.json @@ -0,0 +1,25 @@ +{ + "name": "minimal-package", + "version": "1.0.0", + "description": null, + "authors": null, + "license": null, + "license_file": null, + "readme": null, + "homepage": null, + "repository": null, + "documentation": null, + "targets": { + "default_target": { + "host_dependencies": {}, + "build_dependencies": {}, + "run_dependencies": { + "python": { + "binary": { + "version": ">=3.8" + } + } + } + } + } +} diff --git a/crates/pixi-build-backend/tests/integration/protocol.rs b/crates/pixi-build-backend/tests/integration/protocol.rs index 7debfe10..de60f7b1 100644 --- a/crates/pixi-build-backend/tests/integration/protocol.rs +++ b/crates/pixi-build-backend/tests/integration/protocol.rs @@ -5,10 +5,14 @@ use imp::TestGenerateRecipe; use pixi_build_backend::{intermediate_backend::IntermediateBackend, protocol::Protocol}; use pixi_build_types::{ ChannelConfiguration, PlatformAndVirtualPackages, - procedures::{conda_build_v0::CondaBuildParams, conda_metadata::CondaMetadataParams}, + procedures::{ + conda_build_v0::CondaBuildParams, + conda_build_v1::{CondaBuildV1Output, CondaBuildV1Params}, + conda_metadata::CondaMetadataParams, + }, }; use rattler_build::console_utils::LoggingOutputHandler; -use rattler_conda_types::Platform; +use rattler_conda_types::{ChannelUrl, Platform}; use serde_json::json; use tempfile::TempDir; use url::Url; @@ -201,3 +205,78 @@ async fn test_conda_build() { ".packages[0].subdir" => "[redacted]", }); } + +#[tokio::test] +async fn test_conda_build_v1() { + let tmp_dir = TempDir::new().unwrap(); + let tmp_dir_path = tmp_dir.path().to_path_buf(); + + let pixi_manifest = tmp_dir_path.join("pixi.toml"); + let build_dir = tmp_dir_path.join("build"); + + // Load a model from JSON + let original_model = load_project_model_from_json("minimal_project_model_for_build.json"); + + // Serialize it back to JSON + let project_model_v1 = convert_test_model_to_project_model_v1(original_model); + + // save the pixi.toml file to a temporary location + fs_err::write(&pixi_manifest, toml::to_string(&project_model_v1).unwrap()).unwrap(); + + let channel_url = Url::parse("https://prefix.dev/conda-forge").unwrap(); + + let channel_url = ChannelUrl::from(channel_url); + + let build_params = CondaBuildV1Params { + channels: vec![channel_url], + + build_prefix: None, + host_prefix: None, + run_constraints: None, + run_dependencies: None, + run_exports: None, + output: CondaBuildV1Output { + name: "minimal-package".parse().unwrap(), + version: None, + build: None, + subdir: Platform::current(), + variant: Default::default(), + }, + work_directory: build_dir.clone(), + output_directory: None, + editable: None, + }; + + let some_config = json!({ + "debug-dir": "some_debug_dir", + }); + + let target_config = Default::default(); + + let intermediate_backend: IntermediateBackend = IntermediateBackend::new( + pixi_manifest.clone(), + Some(tmp_dir_path.clone()), + project_model_v1, + Arc::default(), + some_config, + target_config, + LoggingOutputHandler::default(), + None, + ) + .unwrap(); + + let conda_build_result = intermediate_backend + .conda_build_v1(build_params) + .await + .unwrap(); + + insta::assert_yaml_snapshot!(conda_build_result, { + ".output_file" => "[redacted]", + ".build" => "[redacted]", + ".subdir" => "[redacted]", + }); + + // we also want to assert that we put intermediate_recipe.yaml in the debug dir + + assert!(build_dir.join("work").join("recipe.yaml").exists()); +} diff --git a/crates/pixi-build-backend/tests/integration/snapshots/integration__protocol__conda_build_v1.snap b/crates/pixi-build-backend/tests/integration/snapshots/integration__protocol__conda_build_v1.snap new file mode 100644 index 00000000..0f9eb721 --- /dev/null +++ b/crates/pixi-build-backend/tests/integration/snapshots/integration__protocol__conda_build_v1.snap @@ -0,0 +1,10 @@ +--- +source: crates/pixi-build-backend/tests/integration/protocol.rs +expression: conda_build_result +--- +output_file: "[redacted]" +input_globs: [] +name: minimal-package +version: 1.0.0 +build: "[redacted]" +subdir: "[redacted]"