Skip to content

Commit

Permalink
feat: pixi add git source dependency (#2858)
Browse files Browse the repository at this point in the history
  • Loading branch information
nichmor authored Jan 10, 2025
1 parent 8556169 commit d1df544
Show file tree
Hide file tree
Showing 23 changed files with 1,272 additions and 229 deletions.
412 changes: 249 additions & 163 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion crates/pixi_manifest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pep508_rs = { workspace = true }
pixi_consts = { workspace = true }
pixi_spec = { workspace = true }
pixi_toml = { workspace = true }
pixi_utils = { workspace = true }
regex = { workspace = true }
serde = { workspace = true }
serde-value = { workspace = true }
Expand Down
64 changes: 40 additions & 24 deletions crates/pixi_manifest/src/manifests/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use indexmap::{Equivalent, IndexSet};
use itertools::Itertools;
use miette::{miette, IntoDiagnostic, NamedSource, Report, WrapErr};
use pixi_spec::PixiSpec;
use rattler_conda_types::{ChannelConfig, MatchSpec, PackageName, Platform, Version};
use rattler_conda_types::{PackageName, Platform, Version};
use toml_edit::{DocumentMut, Value};

use crate::toml::FromTomlStr;
Expand Down Expand Up @@ -372,34 +372,26 @@ impl Manifest {
Ok(())
}

/// Add a matchspec to the manifest
/// Add a pixi spec to the manifest
pub fn add_dependency(
&mut self,
spec: &MatchSpec,
name: &PackageName,
spec: &PixiSpec,
spec_type: SpecType,
platforms: &[Platform],
feature_name: &FeatureName,
overwrite_behavior: DependencyOverwriteBehavior,
channel_config: &ChannelConfig,
) -> miette::Result<bool> {
// Determine the name of the package to add
let (Some(name), spec) = spec.clone().into_nameless() else {
miette::bail!(
"{} does not support wildcard dependencies",
pixi_utils::executable_name()
);
};
let spec = PixiSpec::from_nameless_matchspec(spec, channel_config);
let mut any_added = false;
for platform in to_options(platforms) {
// Add the dependency to the manifest
match self
.get_or_insert_target_mut(platform, Some(feature_name))
.try_add_dependency(&name, &spec, spec_type, overwrite_behavior)
.try_add_dependency(name, spec, spec_type, overwrite_behavior)
{
Ok(true) => {
self.source
.add_dependency(&name, &spec, spec_type, platform, feature_name)?;
.add_dependency(name, spec, spec_type, platform, feature_name)?;
any_added = true;
}
Ok(false) => {}
Expand Down Expand Up @@ -813,8 +805,8 @@ mod tests {
use insta::assert_snapshot;
use miette::NarratableReportHandler;
use rattler_conda_types::{
NamedChannelOrUrl, ParseStrictness,
ParseStrictness::{Lenient, Strict},
MatchSpec, NamedChannelOrUrl,
ParseStrictness::{self, Lenient, Strict},
VersionSpec,
};
use rstest::*;
Expand Down Expand Up @@ -2062,14 +2054,22 @@ bar = "*"
"#;
let channel_config = default_channel_config();
let mut manifest = Manifest::from_str(Path::new("pixi.toml"), file_contents).unwrap();
// Determine the name of the package to add
let spec = &MatchSpec::from_str("baz >=1.2.3", Strict).unwrap();

let (name, spec) = spec.clone().into_nameless();
let name = name.unwrap();

let spec = PixiSpec::from_nameless_matchspec(spec, &channel_config);

manifest
.add_dependency(
&MatchSpec::from_str("baz >=1.2.3", Strict).unwrap(),
&name,
&spec,
SpecType::Run,
&[],
&FeatureName::Default,
DependencyOverwriteBehavior::Overwrite,
&channel_config,
)
.unwrap();
assert_eq!(
Expand All @@ -2085,14 +2085,20 @@ bar = "*"
.as_version_spec(),
Some(&VersionSpec::from_str(">=1.2.3", Strict).unwrap())
);

let (name, spec) = MatchSpec::from_str("bal >=2.3", Strict)
.unwrap()
.into_nameless();
let pixi_spec = PixiSpec::from_nameless_matchspec(spec, &channel_config);

manifest
.add_dependency(
&MatchSpec::from_str(" bal >=2.3", Strict).unwrap(),
&name.unwrap(),
&pixi_spec,
SpecType::Run,
&[],
&FeatureName::Named("test".to_string()),
DependencyOverwriteBehavior::Overwrite,
&channel_config,
)
.unwrap();

Expand All @@ -2113,14 +2119,19 @@ bar = "*"
">=2.3".to_string()
);

let (package_name, nameless) = MatchSpec::from_str(" boef >=2.3", Strict)
.unwrap()
.into_nameless();
let pixi_spec = PixiSpec::from_nameless_matchspec(nameless, &channel_config);

manifest
.add_dependency(
&MatchSpec::from_str(" boef >=2.3", Strict).unwrap(),
&package_name.unwrap(),
&pixi_spec,
SpecType::Run,
&[Platform::Linux64],
&FeatureName::Named("extra".to_string()),
DependencyOverwriteBehavior::Overwrite,
&channel_config,
)
.unwrap();

Expand All @@ -2142,14 +2153,19 @@ bar = "*"
">=2.3".to_string()
);

let matchspec = MatchSpec::from_str(" cmake >=2.3", ParseStrictness::Strict).unwrap();
let (package_name, nameless) = matchspec.into_nameless();

let pixi_spec = PixiSpec::from_nameless_matchspec(nameless, &channel_config);

manifest
.add_dependency(
&MatchSpec::from_str(" cmake >=2.3", ParseStrictness::Strict).unwrap(),
&package_name.unwrap(),
&pixi_spec,
SpecType::Build,
&[Platform::Linux64],
&FeatureName::Named("build".to_string()),
DependencyOverwriteBehavior::Overwrite,
&channel_config,
)
.unwrap();

Expand Down
59 changes: 55 additions & 4 deletions crates/pixi_spec/src/git.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fmt::Display;

use pixi_git::git::GitReference;
use serde::{Serialize, Serializer};
use thiserror::Error;
use url::Url;

Expand All @@ -12,7 +13,7 @@ pub struct GitSpec {
pub git: Url,

/// The git revision of the package
#[serde(skip_serializing_if = "Option::is_none", flatten)]
#[serde(skip_serializing_if = "Reference::is_default_branch", flatten)]
pub rev: Option<Reference>,

/// The git subdirectory of the package
Expand All @@ -21,9 +22,7 @@ pub struct GitSpec {
}

/// A reference to a specific commit in a git repository.
#[derive(
Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, ::serde::Serialize, ::serde::Deserialize,
)]
#[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, ::serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Reference {
/// The HEAD commit of a branch.
Expand All @@ -39,6 +38,16 @@ pub enum Reference {
DefaultBranch,
}

impl Reference {
/// Returns the reference as a string.
pub fn is_default_branch(reference: &Option<Reference>) -> bool {
reference.is_none()
|| reference
.as_ref()
.is_some_and(|reference| matches!(reference, Reference::DefaultBranch))
}
}

impl Display for Reference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand All @@ -65,6 +74,48 @@ impl From<GitReference> for Reference {
}
}

impl Serialize for Reference {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct RawReference<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
tag: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
branch: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
rev: Option<&'a str>,
}

let ser = match self {
Reference::Branch(name) => RawReference {
branch: Some(name),
tag: None,
rev: None,
},
Reference::Tag(name) => RawReference {
branch: None,
tag: Some(name),
rev: None,
},
Reference::Rev(name) => RawReference {
branch: None,
tag: None,
rev: Some(name),
},
Reference::DefaultBranch => RawReference {
branch: None,
tag: None,
rev: None,
},
};

ser.serialize(serializer)
}
}

#[derive(Error, Debug)]
pub enum GitReferenceError {
#[error("The commit string is invalid: \"{0}\"")]
Expand Down
44 changes: 30 additions & 14 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ These dependencies will be read by pixi as if they had been added to the pixi `p
- `--pypi`: Specifies a PyPI dependency, not a conda package.
Parses dependencies as [PEP508](https://peps.python.org/pep-0508/) requirements, supporting extras and versions.
See [configuration](pixi_manifest.md) for details.
- `--git`: Specifies a git dependency, the package will be installed from the git repository.
The `--git` flag can be used with the following options:
- `--branch <BRANCH>`: The branch to use when installing the package.
- `--tag <TAG>`: The tag to use when installing the package.
- `--rev <REV>`: The revision to use when installing the package.
- `--subdir <SUBDIR>`: The subdirectory to use when installing the package.
- `--no-install`: Don't install the package to the environment, only add the package to the lock-file.
- `--no-lockfile-update`: Don't update the lock-file, implies the `--no-install` flag.
- `--platform <PLATFORM> (-p)`: The platform for which the dependency should be added. (Allowed to be used more than once)
Expand All @@ -98,15 +104,20 @@ pixi add --platform osx-64 clang # (7)!
pixi add --no-install numpy # (8)!
pixi add --no-lockfile-update numpy # (9)!
pixi add --feature featurex numpy # (10)!
pixi add --git https://github.com/wolfv/pixi-build-examples boost-check # (11)!
pixi add --git https://github.com/wolfv/pixi-build-examples --branch main --subdir boost-check boost-check # (12)!
pixi add --git https://github.com/wolfv/pixi-build-examples --tag v0.1.0 boost-check # (13)!
pixi add --git https://github.com/wolfv/pixi-build-examples --rev e50d4a1 boost-check # (14)!

# Add a pypi dependency
pixi add --pypi requests[security] # (11)!
pixi add --pypi Django==5.1rc1 # (12)!
pixi add --pypi "boltons>=24.0.0" --feature lint # (13)!
pixi add --pypi "boltons @ https://files.pythonhosted.org/packages/46/35/e50d4a115f93e2a3fbf52438435bb2efcf14c11d4fcd6bdcd77a6fc399c9/boltons-24.0.0-py3-none-any.whl" # (14)!
pixi add --pypi "exchangelib @ git+https://github.com/ecederstrand/exchangelib" # (15)!
pixi add --pypi "project @ file:///absolute/path/to/project" # (16)!
pixi add --pypi "project@file:///absolute/path/to/project" --editable # (17)!
pixi add --pypi requests[security] # (15)!
pixi add --pypi Django==5.1rc1 # (16)!
pixi add --pypi "boltons>=24.0.0" --feature lint # (17)!
pixi add --pypi "boltons @ https://files.pythonhosted.org/packages/46/35/e50d4a115f93e2a3fbf52438435bb2efcf14c11d4fcd6bdcd77a6fc399c9/boltons-24.0.0-py3-none-any.whl" # (18)!
pixi add --pypi "exchangelib @ git+https://github.com/ecederstrand/exchangelib" # (19)!
pixi add --pypi "project @ file:///absolute/path/to/project" # (20)!
pixi add --pypi "project@file:///absolute/path/to/project" --editable # (21)!
pixi add --git https://github.com/mahmoud/boltons.git boltons --pypi # (22)!
```

1. This will add the `numpy` package to the project with the latest available for the solved environment.
Expand All @@ -119,13 +130,18 @@ pixi add --pypi "project@file:///absolute/path/to/project" --editable # (17)!
8. This will add the `numpy` package to the manifest and lockfile, without installing it in an environment.
9. This will add the `numpy` package to the manifest without updating the lockfile or installing it in the environment.
10. This will add the `numpy` package in the feature `featurex`.
11. This will add the `requests` package as `pypi` dependency with the `security` extra.
12. This will add the `pre-release` version of `Django` to the project as a `pypi` dependency.
13. This will add the `boltons` package in the feature `lint` as `pypi` dependency.
14. This will add the `boltons` package with the given `url` as `pypi` dependency.
15. This will add the `exchangelib` package with the given `git` url as `pypi` dependency.
16. This will add the `project` package with the given `file` url as `pypi` dependency.
17. This will add the `project` package with the given `file` url as an `editable` package as `pypi` dependency.
11. This will add the `boost-check` source package to the dependencies from the git repository.
12. This will add the `boost-check` source package to the dependencies from the git repository using `main` branch and the `boost-check` folder in the repository.
13. This will add the `boost-check` source package to the dependencies from the git repository using `v0.1.0` tag.
14. This will add the `boost-check` source package to the dependencies from the git repository using `e50d4a1` revision.
15. This will add the `requests` package as `pypi` dependency with the `security` extra.
16. This will add the `pre-release` version of `Django` to the project as a `pypi` dependency.
17. This will add the `boltons` package in the feature `lint` as `pypi` dependency.
18. This will add the `boltons` package with the given `url` as `pypi` dependency.
19. This will add the `exchangelib` package with the given `git` url as `pypi` dependency.
20. This will add the `project` package with the given `file` url as `pypi` dependency.
21. This will add the `project` package with the given `file` url as an `editable` package as `pypi` dependency.
22. This will add the `boltons` package with the given `git` url as `pypi` dependency. `branch`, `tag`, and `rev` are not yet supported.

!!! tip
If you want to use a non default pinning strategy, you can set it using [pixi's configuration](./pixi_configuration.md#pinning-strategy).
Expand Down
Loading

0 comments on commit d1df544

Please sign in to comment.