Skip to content

Commit 766555b

Browse files
committed
wip: support outputs for develop feature
1 parent 997e386 commit 766555b

File tree

8 files changed

+163
-971
lines changed

8 files changed

+163
-971
lines changed

crates/pixi/tests/integration_rust/common/package_database.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use itertools::Itertools;
99
use miette::IntoDiagnostic;
1010
use rattler_conda_types::{
1111
ChannelInfo, PackageName, PackageRecord, PackageUrl, Platform, RepoData, VersionWithSource,
12-
package::ArchiveType,
12+
package::{ArchiveType, RunExportsJson},
1313
};
1414
use std::{collections::HashSet, path::Path};
1515
use tempfile::TempDir;
@@ -294,7 +294,7 @@ impl PackageBuilder {
294294
track_features: vec![],
295295
version: self.version,
296296
purls: self.purls,
297-
run_exports: None,
297+
run_exports: Some(RunExportsJson::default()),
298298
python_site_packages_path: None,
299299
experimental_extra_depends: Default::default(),
300300
},

crates/pixi/tests/integration_rust/develop_dependencies_tests.rs

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ async fn test_develop_dependencies_basic() {
6262
setup_tracing();
6363

6464
// Create a package database with common dependencies
65-
let mut package_database = create_test_package_database();
66-
package_database.add_package(Package::build("empty-backend", "0.1.0").finish());
65+
let package_database = create_test_package_database();
6766

6867
// Convert to channel
6968
let channel = package_database.into_channel().await.unwrap();
@@ -156,8 +155,7 @@ async fn test_develop_dependencies_with_source_dependencies() {
156155
setup_tracing();
157156

158157
// Create a package database
159-
let mut package_database = create_test_package_database();
160-
package_database.add_package(Package::build("empty-backend", "0.1.0").finish());
158+
let package_database = create_test_package_database();
161159

162160
let channel = package_database.into_channel().await.unwrap();
163161

@@ -273,8 +271,7 @@ package-a = {{ path = "./package-a" }}
273271
async fn test_develop_dependencies_with_cross_references() {
274272
setup_tracing();
275273

276-
let mut package_database = create_test_package_database();
277-
package_database.add_package(Package::build("empty-backend", "0.1.0").finish());
274+
let package_database = create_test_package_database();
278275

279276
let channel = package_database.into_channel().await.unwrap();
280277

@@ -379,8 +376,7 @@ package-y = {{ path = "{}" }}
379376
async fn test_develop_dependencies_in_features() {
380377
setup_tracing();
381378

382-
let mut package_database = create_test_package_database();
383-
package_database.add_package(Package::build("empty-backend", "0.1.0").finish());
379+
let package_database = create_test_package_database();
384380

385381
let channel = package_database.into_channel().await.unwrap();
386382

@@ -445,13 +441,127 @@ feature-package = {{ path = "./feature-package" }}
445441
);
446442
}
447443

444+
/// Test that a source package can be listed both in [develop] and in dependencies
445+
/// without causing conflicts (the package is essentially included twice, once as a develop dep
446+
/// and once as a regular source dep)
447+
#[tokio::test]
448+
async fn test_develop_and_regular_dependency_same_package() {
449+
setup_tracing();
450+
451+
let package_database = create_test_package_database();
452+
453+
let channel = package_database.into_channel().await.unwrap();
454+
455+
let backend_override = BackendOverride::from_memory(PassthroughBackend::instantiator());
456+
let pixi = PixiControl::new()
457+
.unwrap()
458+
.with_backend_override(backend_override);
459+
460+
// Create a shared package that will be both a develop dependency and a regular dependency
461+
let shared_package_path = create_source_package(
462+
pixi.workspace_path(),
463+
"shared-package",
464+
"1.0.0",
465+
r#"
466+
[package.host-dependencies]
467+
python = ">=3.8"
468+
"#,
469+
);
470+
471+
// Create another package that depends on shared-package as a regular source dependency
472+
let _dependent_package = create_source_package(
473+
pixi.workspace_path(),
474+
"dependent-package",
475+
"1.0.0",
476+
&format!(
477+
r#"
478+
[package.run-dependencies]
479+
shared-package = {{ path = "{}" }}
480+
numpy = ">=1.0"
481+
"#,
482+
shared_package_path.to_string_lossy().replace('\\', "\\\\")
483+
),
484+
);
485+
486+
// Create a manifest that:
487+
// 1. Lists shared-package as a develop dependency
488+
// 2. Lists dependent-package as a regular source dependency
489+
// This means shared-package appears both as a develop dep and as a transitive source dep
490+
let manifest_content = format!(
491+
r#"
492+
[workspace]
493+
channels = ["{}"]
494+
platforms = ["{}"]
495+
preview = ["pixi-build"]
496+
497+
[dependencies]
498+
dependent-package = {{ path = "./dependent-package" }}
499+
500+
[develop]
501+
shared-package = {{ path = "{}" }}
502+
"#,
503+
channel.url(),
504+
Platform::current(),
505+
shared_package_path.to_string_lossy().replace('\\', "\\\\")
506+
);
507+
508+
fs::write(pixi.manifest_path(), manifest_content).unwrap();
509+
510+
// Update the lock-file - this should work without conflicts
511+
let lock_file = pixi.update_lock_file().await.unwrap();
512+
513+
// Verify that python is in the lock-file (from shared-package's dependencies)
514+
assert!(
515+
lock_file.contains_conda_package(
516+
consts::DEFAULT_ENVIRONMENT_NAME,
517+
Platform::current(),
518+
"python",
519+
),
520+
"python should be in the lock-file (run dependency of shared-package)"
521+
);
522+
523+
// Verify that numpy is in the lock-file (from dependent-package's dependencies)
524+
assert!(
525+
lock_file.contains_conda_package(
526+
consts::DEFAULT_ENVIRONMENT_NAME,
527+
Platform::current(),
528+
"numpy",
529+
),
530+
"numpy should be in the lock-file (run dependency of dependent-package)"
531+
);
532+
533+
// Verify that dependent-package IS in the lock-file (it's a regular source dependency)
534+
assert!(
535+
lock_file.contains_conda_package(
536+
consts::DEFAULT_ENVIRONMENT_NAME,
537+
Platform::current(),
538+
"dependent-package",
539+
),
540+
"dependent-package SHOULD be in the lock-file (it's a regular source dependency)"
541+
);
542+
543+
// Key assertion: shared-package WILL appear in the lock-file as a built package
544+
// because it's a source dependency of dependent-package.
545+
// The fact that it's also in [develop] doesn't prevent it from being built when
546+
// it's needed as a dependency of another package.
547+
// This is correct behavior - [develop] means "install my dependencies without building me",
548+
// but if another package needs it built, it will be built.
549+
assert!(
550+
lock_file.contains_conda_package(
551+
consts::DEFAULT_ENVIRONMENT_NAME,
552+
Platform::current(),
553+
"shared-package",
554+
),
555+
"shared-package SHOULD be in the lock-file (it's built as a source dependency of dependent-package)"
556+
);
557+
}
558+
448559
/// Test that platform-specific develop dependencies work correctly
449560
#[tokio::test]
450561
async fn test_develop_dependencies_platform_specific() {
451562
setup_tracing();
452563

453-
let mut package_database = create_test_package_database();
454-
package_database.add_package(Package::build("empty-backend", "0.1.0").finish());
564+
let package_database = create_test_package_database();
455565

456566
let channel = package_database.into_channel().await.unwrap();
457567

crates/pixi_command_dispatcher/src/command_dispatcher/mod.rs

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -505,81 +505,7 @@ impl CommandDispatcher {
505505
self.execute_task(spec).await
506506
}
507507

508-
/// Returns the dependencies (build, host, and run) for a specific output
509-
/// from a source package.
510508
///
511-
/// This method retrieves metadata from the build backend for the given
512-
/// source and extracts the dependencies for the specified output name.
513-
/// The dependencies are returned in their raw form as specified by the
514-
/// backend, including both regular dependencies and constraints.
515-
///
516-
/// This is useful when you want to install the dependencies of a source
517-
/// package without actually building it - for instance, to set up a
518-
/// development environment or to inspect what would be needed to build
519-
/// the package.
520-
///
521-
/// # Requirements
522-
///
523-
/// - The build backend must support the `conda/outputs` procedure (API v1+)
524-
/// - The specified output must exist in the source package
525-
pub async fn get_output_dependencies(
526-
&self,
527-
spec: crate::GetOutputDependenciesSpec,
528-
) -> Result<crate::OutputDependencies, CommandDispatcherError<crate::GetOutputDependenciesError>>
529-
{
530-
spec.request(self.clone()).await
531-
}
532-
533-
/// Expands a list of dev sources into their dependencies.
534-
///
535-
/// Dev sources are source packages whose dependencies should be installed
536-
/// without building the packages themselves. This is particularly useful
537-
/// for development environments where you want to work on a package while
538-
/// having all its dependencies available.
539-
///
540-
/// For each dev source, this method:
541-
/// 1. Checks out the source code
542-
/// 2. Extracts all dependencies (build, host, and run) and constraints
543-
/// 3. Filters out dependencies that are themselves dev sources (to avoid
544-
/// duplication)
545-
///
546-
/// The result can then be merged into a `PixiEnvironmentSpec` and passed to
547-
/// `solve_pixi_environment` to create an environment with all the dependencies
548-
/// but without the dev source packages themselves.
549-
///
550-
/// # Example
551-
///
552-
/// ```ignore
553-
/// // Expand dev sources
554-
/// let expanded = command_dispatcher.expand_dev_sources(spec).await?;
555-
///
556-
/// // Merge into environment spec
557-
/// let mut env_spec = PixiEnvironmentSpec::default();
558-
/// env_spec.dependencies.extend(expanded.dependencies);
559-
/// env_spec.constraints.extend(expanded.constraints);
560-
///
561-
/// // Solve the environment
562-
/// let solved = command_dispatcher.solve_pixi_environment(env_spec).await?;
563-
/// ```
564-
///
565-
/// # Use case
566-
///
567-
/// You're developing two packages (`pkgA` and `pkgB`) where `pkgA` depends
568-
/// on `pkgB`. Instead of building both packages, you can use
569-
/// `expand_dev_sources` to:
570-
/// 1. Get the dependencies of both `pkgA` and `pkgB`
571-
/// 2. Remove `pkgB` from `pkgA`'s dependencies (since it's also a dev source)
572-
/// 3. Create an environment with just the external dependencies
573-
///
574-
/// This allows you to work on the source code of both packages directly.
575-
pub async fn expand_dev_sources(
576-
&self,
577-
spec: crate::ExpandDevSourcesSpec,
578-
) -> Result<crate::ExpandedDevSources, CommandDispatcherError<crate::ExpandDevSourcesError>>
579-
{
580-
spec.request(self.clone()).await
581-
}
582-
583509
/// Calls into a pixi build backend to perform a source build.
584510
pub(crate) async fn backend_source_build(
585511
&self,

0 commit comments

Comments
 (0)