Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/pixi_build_frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["process", "io-std"] }
tokio-util = { workspace = true, features = ["codec"] }
tracing = { workspace = true }
schemars = { workspace = true, optional = true }


[dev-dependencies]
Expand Down
18 changes: 12 additions & 6 deletions crates/pixi_command_dispatcher/src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use dependencies::{
Dependencies, DependenciesError, DependencySource, KnownEnvironment, PixiRunExports, WithSource,
};
pub(crate) use move_file::{MoveError, move_file};
use pixi_record::PinnedSourceSpec;
use pixi_record::{PinnedBuildSourceSpec, PinnedSourceSpec};
use serde::{Deserialize, Serialize};
use url::Url;
pub use work_dir_key::{SourceRecordOrCheckout, WorkDirKey};
Expand All @@ -44,11 +44,14 @@ pub struct SourceCodeLocation {
/// The location of the manifest and the possible source code
manifest_source: PinnedSourceSpec,
/// The location of the source code that should be queried and build
build_source: Option<PinnedSourceSpec>,
build_source: Option<PinnedBuildSourceSpec>,
}

impl SourceCodeLocation {
pub fn new(manifest_source: PinnedSourceSpec, build_source: Option<PinnedSourceSpec>) -> Self {
pub fn new(
manifest_source: PinnedSourceSpec,
build_source: Option<PinnedBuildSourceSpec>,
) -> Self {
Self {
manifest_source,
build_source,
Expand All @@ -64,17 +67,20 @@ impl SourceCodeLocation {
/// This is the normally the path to the manifest_source
/// but when set is the path to the build_source
pub fn source_code(&self) -> &PinnedSourceSpec {
self.build_source.as_ref().unwrap_or(&self.manifest_source)
self.build_source
.as_ref()
.map(PinnedBuildSourceSpec::pinned)
.unwrap_or(&self.manifest_source)
}

/// Get the optional explicit build source override.
pub fn build_source(&self) -> Option<&PinnedSourceSpec> {
pub fn build_source(&self) -> Option<&PinnedBuildSourceSpec> {
self.build_source.as_ref()
}

pub fn as_source_and_alternative_root(&self) -> (&PinnedSourceSpec, Option<&PinnedSourceSpec>) {
if let Some(build_source) = &self.build_source {
(build_source, Some(&self.manifest_source))
(build_source.pinned(), Some(&self.manifest_source))
} else {
(&self.manifest_source, None)
}
Expand Down
44 changes: 32 additions & 12 deletions crates/pixi_command_dispatcher/src/build_backend_metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pixi_build_discovery::{CommandSpec, EnabledProtocols};
use pixi_build_frontend::Backend;
use pixi_build_types::procedures::conda_outputs::CondaOutputsParams;
use pixi_glob::GlobSet;
use pixi_record::{PinnedSourceSpec, VariantValue};
use pixi_record::{PinnedBuildSourceSpec, PinnedSourceSpec, VariantValue};
use pixi_spec::{SourceAnchor, SourceLocationSpec};
use rattler_conda_types::{ChannelConfig, ChannelUrl};
use std::time::SystemTime;
Expand All @@ -32,6 +32,7 @@ use crate::{
use pixi_build_discovery::BackendSpec;
use pixi_build_frontend::BackendOverride;
use pixi_path::AbsPath;
use pixi_path::normalize::normalize_typed;

static WARNED_BACKENDS: Lazy<Mutex<HashSet<String>>> = Lazy::new(|| Mutex::new(HashSet::new()));

Expand Down Expand Up @@ -139,33 +140,52 @@ impl BuildBackendMetadataSpec {
let build_source_checkout = match &discovered_backend.init_params.build_source {
None => None,
Some(build_source) => {
// An out of tree source is provided. Resolve it against the manifest source.
let relative_build_source_spec = if let SourceLocationSpec::Path(path) =
build_source
&& path.path.is_relative()
{
Some(normalize_typed(path.path.to_path()).to_string())
} else {
None
};

// An out-of-tree source is provided. Resolve it against the manifest source.
let resolved_location = manifest_source_anchor.resolve(build_source.clone());

// Check if we have a preferred build source that matches this same location
match &self.preferred_build_source {
Some(pinned) if pinned.matches_source_spec(&resolved_location) => Some(
Some(pinned) if pinned.matches_source_spec(&resolved_location) => Some((
command_dispatcher
.checkout_pinned_source(pinned.clone())
.await
.map_err_with(BuildBackendMetadataError::SourceCheckout)?,
),
_ => Some(
relative_build_source_spec,
)),
_ => Some((
command_dispatcher
.pin_and_checkout(resolved_location)
.await
.map_err_with(BuildBackendMetadataError::SourceCheckout)?,
),
relative_build_source_spec,
)),
}
}
};

let (build_source_checkout, build_source) = if let Some(checkout) = build_source_checkout {
let pinned = checkout.pinned.clone();
(checkout, Some(pinned))
} else {
(manifest_source_checkout.clone(), None)
};
let (build_source_checkout, build_source) =
if let Some((checkout, relative_build_source)) = build_source_checkout {
let pinned = checkout.pinned.clone();
(
checkout,
Some(if let Some(relative) = relative_build_source {
PinnedBuildSourceSpec::Relative(relative, pinned)
} else {
PinnedBuildSourceSpec::Absolute(pinned)
}),
)
} else {
(manifest_source_checkout.clone(), None)
};
let manifest_source_location = SourceCodeLocation::new(
manifest_source_checkout.pinned.clone(),
build_source.clone(),
Expand Down
6 changes: 3 additions & 3 deletions crates/pixi_command_dispatcher/src/source_build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use pixi_build_discovery::EnabledProtocols;
use pixi_build_frontend::Backend;
use pixi_build_types::procedures::conda_outputs::CondaOutputsParams;
use pixi_path::AbsPath;
use pixi_record::{PinnedSourceSpec, PixiRecord, VariantValue};
use pixi_record::{PinnedBuildSourceSpec, PinnedSourceSpec, PixiRecord, VariantValue};
use pixi_spec::{SourceAnchor, SourceLocationSpec};
use rattler_conda_types::{
ChannelConfig, ChannelUrl, ConvertSubdirError, InvalidPackageNameError, PackageRecord,
Expand Down Expand Up @@ -255,7 +255,7 @@ impl SourceBuildSpec {
// manifest so we check out the correct directory.
let mut build_source = self.source.build_source().cloned();
if let (Some(PinnedSourceSpec::Git(pinned_git)), Some(SourceLocationSpec::Git(git_spec))) = (
build_source.as_mut(),
build_source.as_mut().map(PinnedBuildSourceSpec::pinned_mut),
discovered_backend.init_params.build_source.clone(),
) && pinned_git.source.subdirectory.is_none()
{
Expand All @@ -268,7 +268,7 @@ impl SourceBuildSpec {
// 3. Manifest source. Just assume that source is located at the same directory as the manifest.
let build_source_checkout = if let Some(pinned_build_source) = build_source {
&command_dispatcher
.checkout_pinned_source(pinned_build_source)
.checkout_pinned_source(pinned_build_source.into_pinned())
.await
.map_err_with(SourceBuildError::SourceCheckout)?
} else if let Some(manifest_build_source) =
Expand Down
14 changes: 10 additions & 4 deletions crates/pixi_core/src/lock_file/satisfiability/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use pixi_manifest::{
pypi::pypi_options::{NoBuild, PrereleaseMode},
};
use pixi_record::{
DevSourceRecord, LockedGitUrl, ParseLockFileError, PinnedSourceSpec, PixiRecord,
SourceMismatchError, SourceRecord, VariantValue,
DevSourceRecord, LockedGitUrl, ParseLockFileError, PinnedBuildSourceSpec, PinnedSourceSpec,
PixiRecord, SourceMismatchError, SourceRecord, VariantValue,
};
use pixi_spec::{PixiSpec, SourceAnchor, SourceLocationSpec, SourceSpec, SpecConversionError};
use pixi_utils::variants::VariantConfig;
Expand Down Expand Up @@ -1100,7 +1100,10 @@ async fn verify_source_metadata(
package: source_record.package_record.name.clone(),
backend_metadata: BuildBackendMetadataSpec {
manifest_source: source_record.manifest_source.clone(),
preferred_build_source: source_record.build_source.clone(),
preferred_build_source: source_record
.build_source
.clone()
.map(PinnedBuildSourceSpec::into_pinned),
channel_config,
channels: channel_urls,
build_environment: BuildEnvironment {
Expand Down Expand Up @@ -2298,7 +2301,10 @@ fn verify_build_source_matches_manifest(
))
};

match (manifest_source_location, lockfile_source_location) {
match (
manifest_source_location,
lockfile_source_location.map(PinnedBuildSourceSpec::into_pinned),
) {
(None, None) => ok,
(Some(SourceLocationSpec::Url(murl_spec)), Some(PinnedSourceSpec::Url(lurl_spec))) => {
lurl_spec.satisfies(&murl_spec).map_err(sat_err)
Expand Down
4 changes: 3 additions & 1 deletion crates/pixi_core/src/lock_file/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1625,7 +1625,9 @@ impl<'p> UpdateContext<'p> {
PixiRecord::Source(src) => {
let name = src.package_record.name.clone();
if targets.contains(name.as_source()) {
src.build_source.clone().map(|spec| (name, spec))
src.build_source
.clone()
.map(|spec| (name, spec.into_pinned()))
} else {
None
}
Expand Down
1 change: 1 addition & 0 deletions crates/pixi_path/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ version = "0.1.0"
fs-err = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
typed-path = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
2 changes: 2 additions & 0 deletions crates/pixi_path/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ use std::ops::Deref;
use std::path::{Component, Path, PathBuf};
use thiserror::Error;

pub mod normalize;

/// Error type for path validation failures.
#[derive(Debug, Error)]
pub enum PathError {
Expand Down
103 changes: 103 additions & 0 deletions crates/pixi_path/src/normalize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use typed_path::{
Utf8Component, Utf8Encoding, Utf8Path, Utf8PathBuf, Utf8TypedPath, Utf8TypedPathBuf,
};

/// A slightly modified version of [`Utf8TypedPath::normalize`] that retains
/// `..` components that lead outside the path.
pub fn normalize_typed(path: Utf8TypedPath<'_>) -> Utf8TypedPathBuf {
match path {
Utf8TypedPath::Unix(path) => Utf8TypedPathBuf::Unix(normalize(path)),
Utf8TypedPath::Windows(path) => Utf8TypedPathBuf::Windows(normalize(path)),
}
}

/// A slightly modified version of [`Utf8Path::normalize`] that retains `..`
/// components that lead outside the path.
fn normalize<T: Utf8Encoding>(path: &Utf8Path<T>) -> Utf8PathBuf<T> {
let mut components = Vec::new();
for component in path.components() {
if !component.is_current() && !component.is_parent() {
components.push(component);
} else if component.is_parent() {
if let Some(last) = components.last() {
if last.is_normal() {
components.pop();
} else {
components.push(component);
}
} else {
components.push(component);
}
}
}

let mut path = Utf8PathBuf::<T>::new();

for component in components {
path.push(component.as_str());
}

path
}

#[cfg(test)]
mod tests {
use super::*;
use typed_path::Utf8TypedPath;

#[test]
fn test_normalize_collapses_parent_in_middle() {
// a/b/../c -> a/c
let path = Utf8TypedPath::derive("a/b/../c");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "a/c");
}

#[test]
fn test_normalize_retains_leading_parent() {
// ../a -> ../a (cannot collapse leading ..)
let path = Utf8TypedPath::derive("../a");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "../a");
}

#[test]
fn test_normalize_retains_multiple_leading_parents() {
// ../../a/b -> ../../a/b
let path = Utf8TypedPath::derive("../../a/b");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "../../a/b");
}

#[test]
fn test_normalize_collapses_current_dir() {
// a/./b -> a/b
let path = Utf8TypedPath::derive("a/./b");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "a/b");
}

#[test]
fn test_normalize_complex_path() {
// a/b/c/../../d -> a/d
let path = Utf8TypedPath::derive("a/b/c/../../d");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "a/d");
}

#[test]
fn test_normalize_parent_escapes_base() {
// a/../.. -> .. (goes outside the base)
let path = Utf8TypedPath::derive("a/../..");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "..");
}

#[test]
fn test_normalize_relative_going_up_then_down() {
// ../sibling/subdir -> ../sibling/subdir
let path = Utf8TypedPath::derive("../sibling/subdir");
let normalized = normalize_typed(path);
assert_eq!(normalized.as_str(), "../sibling/subdir");
}
}
Loading
Loading