Skip to content
Draft
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
1 change: 1 addition & 0 deletions Cargo.lock

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

461 changes: 439 additions & 22 deletions crates/pixi_cli/src/global/global_specs.rs

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions crates/pixi_core/src/environment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use rattler_lock::{LockFile, LockedPackage};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::{
collections::HashMap,
collections::{BTreeMap, HashMap},
hash::{Hash, Hasher},
io::ErrorKind,
path::{Path, PathBuf},
Expand Down Expand Up @@ -386,6 +386,17 @@ pub struct EnvironmentFile {
/// be weaker than [`Self::resolved_platform`]. `None` as above.
#[serde(default)]
pub minimum_supported_platform: Option<PlatformData>,
/// Fingerprints of the manifest's source dependencies at install time,
/// keyed by package name: a hash of the source spec plus any inline
/// package definition. Only written by `pixi global`, which has no lock
/// file; `pixi global sync` compares them against the manifest to decide
/// whether a source dependency's *specification* changed (source content
/// changes are `pixi global update`'s job). Empty for workspace
/// environments and environments written by an older pixi — for
/// environments with source dependencies that means out-of-sync, which
/// degrades gracefully to a one-time rebuild.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub source_fingerprints: BTreeMap<String, u64>,
}

/// The path to the environment file in the `conda-meta` directory of the
Expand Down Expand Up @@ -434,9 +445,7 @@ pub fn write_environment_file(

/// Reading the environment file of the environment.
/// Removing it if it's not valid.
pub(crate) fn read_environment_file(
environment_dir: &Path,
) -> miette::Result<Option<EnvironmentFile>> {
pub fn read_environment_file(environment_dir: &Path) -> miette::Result<Option<EnvironmentFile>> {
let path = environment_file_path(environment_dir);

let contents = match fs_err::read_to_string(&path) {
Expand Down
1 change: 1 addition & 0 deletions crates/pixi_core/src/lock_file/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ impl<'p> LockFileDerivedData<'p> {
environment_lock_file_hash: hash,
resolved_platform,
minimum_supported_platform,
source_fingerprints: Default::default(),
},
)?;

Expand Down
1 change: 1 addition & 0 deletions crates/pixi_global/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ toml-span = { workspace = true }
toml_edit = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
xxhash-rust = { workspace = true, features = ["xxh3"] }
zstd = { workspace = true }

[target.'cfg(unix)'.dependencies]
Expand Down
107 changes: 106 additions & 1 deletion crates/pixi_global/src/project/global_spec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
//! This module makes it a bit easier to pass around a package name and the pixi specification
use std::path::Path;

use pixi_manifest::{
InlinePackageManifest, KnownPreviewFeature, Preview,
toml::{TomlPackage, WorkspacePackageProperties},
};
use pixi_spec::PixiSpec;
use rattler_conda_types::{
MatchSpec, NamelessMatchSpec, PackageName, ParseMatchSpecOptions, RepodataRevision,
Expand All @@ -10,6 +16,10 @@ use rattler_conda_types::{
pub struct GlobalSpec {
pub name: PackageName,
pub spec: PixiSpec,
/// An inline package definition (`package = { ... }`) accompanying a
/// source spec: it describes how the source is built when the source does
/// not provide its own package manifest (or to override the one it has).
pub inline: Option<InlinePackageValue>,
}

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
Expand All @@ -23,7 +33,17 @@ pub enum FromMatchSpecError {
impl GlobalSpec {
/// Creates a new `GlobalSpec` with a package name and a Pixi specification.
pub fn new(name: PackageName, spec: PixiSpec) -> Self {
Self { name, spec }
Self {
name,
spec,
inline: None,
}
}

/// Attaches an inline package definition to this spec.
pub fn with_inline(mut self, inline: InlinePackageValue) -> Self {
self.inline = Some(inline);
self
}

/// Returns the package name.
Expand Down Expand Up @@ -63,3 +83,88 @@ impl GlobalSpec {
Ok(GlobalSpec::new(name.clone(), pixi_spec))
}
}

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum InlinePackageValueError {
#[error("failed to parse the inline package definition: {0}")]
Parse(String),
#[error("an inline package definition cannot set `name`; it is taken from the dependency key")]
ExplicitName,
#[error(
"an inline package definition cannot set `build.source`; the source is taken from the dependency spec"
)]
ExplicitBuildSource,
#[error("failed to convert the inline package definition: {0}")]
Convert(String),
}

/// An inline package definition as the TOML value of the `package` key of a
/// dependency entry (e.g. `package.build.backend.name = "pixi-build-rust"`).
///
/// The raw TOML representation is kept so the definition can be written
/// verbatim into the global manifest document; [`Self::to_inline_manifest`]
/// converts it into the resolved [`InlinePackageManifest`] when it is needed
/// before the manifest is saved and re-parsed (e.g. for package name
/// inference).
#[derive(Debug, Clone)]
pub struct InlinePackageValue(toml_edit::InlineTable);

impl InlinePackageValue {
/// Wraps a `package` table.
pub fn new(table: toml_edit::InlineTable) -> Self {
Self(table)
}

/// Returns the definition as a TOML value, for insertion into a dependency
/// entry under the `package` key.
pub fn to_toml_value(&self) -> toml_edit::Value {
toml_edit::Value::InlineTable(self.0.clone())
}

/// Parses and converts the definition into an [`InlinePackageManifest`]
/// for the dependency `name`, mirroring the checks of the manifest parser:
/// the definition may not set `name` (it comes from the dependency key)
/// nor `build.source` (it comes from the dependency spec).
pub fn to_inline_manifest(
&self,
name: &PackageName,
root_directory: &Path,
) -> Result<InlinePackageManifest, InlinePackageValueError> {
let source = format!("package = {}", self.0);
let mut value =
toml_span::parse(&source).map_err(|e| InlinePackageValueError::Parse(e.to_string()))?;
let mut th = toml_span::de_helpers::TableHelper::new(&mut value)
.map_err(|e| InlinePackageValueError::Parse(e.to_string()))?;
let (_, mut package_value) = th
.take("package")
.expect("the `package` key was just serialized");
let package = <TomlPackage as toml_span::Deserialize>::deserialize(&mut package_value)
.map_err(|e| {
InlinePackageValueError::Parse(
e.errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; "),
)
})?;

if package.name.is_some() {
return Err(InlinePackageValueError::ExplicitName);
}
if package.build.source.is_some() {
return Err(InlinePackageValueError::ExplicitBuildSource);
}

let preview = Preview::from_iter([KnownPreviewFeature::PixiBuild]);
InlinePackageManifest::from_toml_package(
name,
package,
WorkspacePackageProperties::default(),
&preview,
root_directory,
)
.map(|with_warnings| with_warnings.value)
.map_err(|e| InlinePackageValueError::Convert(e.to_string()))
}
}
Loading
Loading