Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: environment.yaml type #786

Merged
merged 2 commits into from
Jul 23, 2024
Merged
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.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ serde_json = { version = "1.0.116" }
serde_repr = "0.1"
serde_with = "3.7.0"
serde_yaml = "0.9.34"
serde-untagged = "0.1.6"
sha2 = "0.10.8"
shlex = "1.3.0"
similar-asserts = "1.5.0"
Expand Down
5 changes: 4 additions & 1 deletion crates/rattler_conda_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ serde = { workspace = true, features = ["derive", "rc"] }
serde_json = { workspace = true }
serde_repr = { workspace = true }
serde_with = { workspace = true, features = ["indexmap_2"] }
serde-untagged = { workspace = true }
serde_yaml = { workspace = true }
smallvec = { workspace = true, features = ["serde", "const_new", "const_generics", "union"] }
strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tracing = { workspace = true }
typed-path = { workspace = true }
url = { workspace = true, features = ["serde"] }
indexmap = { workspace = true }

[dev-dependencies]
rand = { workspace = true }
insta = { workspace = true, features = ["yaml", "redactions", "toml"] }
insta = { workspace = true, features = ["yaml", "redactions", "toml", "glob"] }
rattler_package_streaming = { path = "../rattler_package_streaming", default-features = false, features = ["rustls-tls"] }
tempfile = { workspace = true }
rstest = { workspace = true }
Expand Down
100 changes: 70 additions & 30 deletions crates/rattler_conda_types/src/channel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{
borrow::Cow,
fmt::{Display, Formatter},
path::{Path, PathBuf},
str::FromStr,
};

use crate::utils::path::is_path;
use crate::utils::url::parse_scheme;
use file_url::directory_path_to_url;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, Serializer};
use thiserror::Error;
use typed_path::{Utf8NativePathBuf, Utf8TypedPath, Utf8TypedPathBuf};
use url::Url;

use super::{ParsePlatformError, Platform};
use crate::utils::{path::is_path, url::parse_scheme};

const DEFAULT_CHANNEL_ALIAS: &str = "https://conda.anaconda.org";

/// The `ChannelConfig` describes properties that are required to resolve "simple" channel names to
/// channel URLs.
/// The `ChannelConfig` describes properties that are required to resolve
/// "simple" channel names to channel URLs.
///
/// When working with [`Channel`]s you want to resolve them to a Url. The Url describes where to
/// find the data in the channel. Working with URLs is less user friendly since most of the time
/// users only use channels from one particular server. Conda solves this by allowing users not to
/// specify a full Url but instead only specify the name of the channel and reading the primary
/// When working with [`Channel`]s you want to resolve them to a Url. The Url
/// describes where to find the data in the channel. Working with URLs is less
/// user friendly since most of the time users only use channels from one
/// particular server. Conda solves this by allowing users not to specify a full
/// Url but instead only specify the name of the channel and reading the primary
/// server address from a configuration file (e.g. `.condarc`).
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
pub struct ChannelConfig {
/// A url to prefix to channel names that don't start with a Url. Usually this Url refers to
/// the `https://conda.anaconda.org` server but users are free to change this. This allows
/// naming channels just by their name instead of their entire Url (e.g. "conda-forge" actually
/// refers to `<https://conda.anaconda.org/conda-forge>`).
/// A url to prefix to channel names that don't start with a Url. Usually
/// this Url refers to the `https://conda.anaconda.org` server but users are free to change this. This allows
/// naming channels just by their name instead of their entire Url (e.g.
/// "conda-forge" actually refers to `<https://conda.anaconda.org/conda-forge>`).
///
/// The default value is: <https://conda.anaconda.org>
pub channel_alias: Url,

/// For local channels, the root directory from which to resolve relative paths.
/// Most of the time you would initialize this with the current working directory.
/// For local channels, the root directory from which to resolve relative
/// paths. Most of the time you would initialize this with the current
/// working directory.
pub root_dir: PathBuf,
}

Expand All @@ -58,7 +61,8 @@ impl ChannelConfig {
}
}

/// Represents a channel description as either a name (e.g. `conda-forge`) or a base url.
/// Represents a channel description as either a name (e.g. `conda-forge`) or a
/// base url.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum NamedChannelOrUrl {
/// A named channel
Expand All @@ -71,7 +75,8 @@ pub enum NamedChannelOrUrl {
impl NamedChannelOrUrl {
/// Returns the string representation of the channel.
///
/// This method ensures that if the channel is a url, it does not end with a `/`.
/// This method ensures that if the channel is a url, it does not end with a
/// `/`.
pub fn as_str(&self) -> &str {
match self {
NamedChannelOrUrl::Name(name) => name,
Expand Down Expand Up @@ -117,11 +122,42 @@ impl Display for NamedChannelOrUrl {
}
}

impl FromStr for NamedChannelOrUrl {
type Err = url::ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if parse_scheme(s).is_some() {
Ok(NamedChannelOrUrl::Url(Url::from_str(s)?))
} else {
Ok(NamedChannelOrUrl::Name(s.to_string()))
}
}
}

impl<'de> serde::Deserialize<'de> for NamedChannelOrUrl {
fn deserialize<D>(deserializer: D) -> Result<NamedChannelOrUrl, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
NamedChannelOrUrl::from_str(&s).map_err(serde::de::Error::custom)
}
}

impl serde::Serialize for NamedChannelOrUrl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.as_str().serialize(serializer)
}
}

/// `Channel`s are the primary source of package information.
#[derive(Debug, Clone, Serialize, Eq, PartialEq, Hash)]
pub struct Channel {
/// The platforms supported by this channel, or None if no explicit platforms have been
/// specified.
/// The platforms supported by this channel, or None if no explicit
/// platforms have been specified.
#[serde(skip_serializing_if = "Option::is_none")]
pub platforms: Option<Vec<Platform>>,

Expand Down Expand Up @@ -247,7 +283,8 @@ impl Channel {
///
/// # Panics
///
/// Panics if the path is not an absolute path or could not be canonicalized.
/// Panics if the path is not an absolute path or could not be
/// canonicalized.
pub fn from_directory(path: &Path) -> Self {
let path = if path.is_absolute() {
Cow::Borrowed(path)
Expand Down Expand Up @@ -279,7 +316,8 @@ impl Channel {
}
}

/// Returns the base Url of the channel. This does not include the platform part.
/// Returns the base Url of the channel. This does not include the platform
/// part.
pub fn base_url(&self) -> &Url {
&self.base_url
}
Expand All @@ -299,8 +337,8 @@ impl Channel {
.collect()
}

/// Returns the platforms explicitly mentioned in the channel or the default platforms of the
/// current system.
/// Returns the platforms explicitly mentioned in the channel or the default
/// platforms of the current system.
pub fn platforms_or_default(&self) -> &[Platform] {
if let Some(platforms) = &self.platforms {
platforms.as_slice()
Expand Down Expand Up @@ -375,8 +413,8 @@ fn parse_platforms(channel: &str) -> Result<(Option<Vec<Platform>>, &str), Parse
Ok((None, channel))
}

/// Returns the default platforms. These are based on the platform this binary was build for as well
/// as platform agnostic platforms.
/// Returns the default platforms. These are based on the platform this binary
/// was build for as well as platform agnostic platforms.
pub(crate) const fn default_platforms() -> &'static [Platform] {
const CURRENT_PLATFORMS: [Platform; 2] = [Platform::current(), Platform::NoArch];
&CURRENT_PLATFORMS
Expand Down Expand Up @@ -405,10 +443,12 @@ fn absolute_path(path: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseC

#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;

use url::Url;

use super::*;

#[test]
fn test_parse_platforms() {
assert_eq!(
Expand Down
Loading
Loading