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

perf: always use sharded repodata for prefix.dev #910

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
76 changes: 76 additions & 0 deletions crates/rattler_conda_types/src/channel/conda_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::fmt::{Display, Formatter};

use serde::{Deserialize, Deserializer, Serialize};
use url::Url;

use crate::Platform;

/// Represents a channel base url. This is a wrapper around an url that is
/// normalized:
///
/// * The URL always contains a trailing `/`.
///
/// This is useful to be able to compare different channels.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize)]
#[serde(transparent)]
pub struct CondaUrl(Url);

impl CondaUrl {
/// Returns the base Url of the channel.
pub fn url(&self) -> &Url {
&self.0
}

/// Returns the string representation of the url.
pub fn as_str(&self) -> &str {
self.0.as_str()
}

/// Append the platform to the base url.
pub fn platform_url(&self, platform: Platform) -> Url {
self.0
.join(&format!("{}/", platform.as_str())) // trailing slash is important here as this signifies a directory
.expect("platform is a valid url fragment")
}
}

impl<'de> Deserialize<'de> for CondaUrl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let url = Url::deserialize(deserializer)?;
Ok(url.into())
}
}

impl From<Url> for CondaUrl {
fn from(url: Url) -> Self {
let path = url.path();
if path.ends_with('/') {
Self(url)
} else {
let mut url = url.clone();
url.set_path(&format!("{path}/"));
Self(url)
}
}
}

impl From<CondaUrl> for Url {
fn from(value: CondaUrl) -> Self {
value.0
}
}

impl AsRef<Url> for CondaUrl {
fn as_ref(&self) -> &Url {
&self.0
}
}

impl Display for CondaUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
95 changes: 41 additions & 54 deletions crates/rattler_conda_types/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use typed_path::{Utf8NativePathBuf, Utf8TypedPath, Utf8TypedPathBuf};
use url::Url;

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

mod conda_url;

pub use conda_url::CondaUrl;

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

Expand Down Expand Up @@ -105,7 +106,7 @@ impl NamedChannelOrUrl {

/// Converts the channel to a base url using the given configuration.
/// This method ensures that the base url always ends with a `/`.
pub fn into_base_url(self, config: &ChannelConfig) -> Result<Url, ParseChannelError> {
pub fn into_base_url(self, config: &ChannelConfig) -> Result<CondaUrl, ParseChannelError> {
let url = match self {
NamedChannelOrUrl::Name(name) => {
let mut base_url = config.channel_alias.clone();
Expand All @@ -114,16 +115,17 @@ impl NamedChannelOrUrl {
segments.push(segment);
}
}
base_url
base_url.into()
}
NamedChannelOrUrl::Url(url) => url,
NamedChannelOrUrl::Url(url) => url.into(),
NamedChannelOrUrl::Path(path) => {
let absolute_path = absolute_path(path.as_str(), &config.root_dir)?;
directory_path_to_url(absolute_path.to_path())
.map_err(|_err| ParseChannelError::InvalidPath(path.to_string()))?
.into()
}
};
Ok(add_trailing_slash(&url).into_owned())
Ok(url)
}

/// Converts this instance into a channel.
Expand All @@ -136,7 +138,8 @@ impl NamedChannelOrUrl {
let base_url = self.into_base_url(config)?;
Ok(Channel {
name,
..Channel::from_url(base_url)
base_url,
platforms: None,
})
}
}
Expand Down Expand Up @@ -189,7 +192,7 @@ pub struct Channel {
pub platforms: Option<Vec<Platform>>,

/// Base URL of the channel, everything is relative to this url.
pub base_url: Url,
pub base_url: CondaUrl,

/// The name of the channel
pub name: Option<String>,
Expand Down Expand Up @@ -221,7 +224,7 @@ impl Channel {
.map_err(|_err| ParseChannelError::InvalidPath(channel.to_owned()))?;
Self {
platforms,
base_url: url,
base_url: url.into(),
name: Some(channel.to_owned()),
}
}
Expand Down Expand Up @@ -252,39 +255,30 @@ impl Channel {
// Get the path part of the URL but trim the directory suffix
let path = url.path().trim_end_matches('/');

// Ensure that the base_url does always ends in a `/`
let base_url = if url.path().ends_with('/') {
url.clone()
} else {
let mut url = url.clone();
url.set_path(&format!("{path}/"));
url
};

// Case 1: No path give, channel name is ""

// Case 2: migrated_custom_channels
// Case 3: migrated_channel_aliases
// Case 4: custom_channels matches
// Case 5: channel_alias match

if base_url.has_host() {
if url.has_host() {
// Case 7: Fallback
let name = path.trim_start_matches('/');
Self {
platforms: None,
name: (!name.is_empty()).then_some(name).map(str::to_owned),
base_url,
base_url: url.into(),
}
} else {
// Case 6: non-otherwise-specified file://-type urls
let name = path
.rsplit_once('/')
.map_or_else(|| base_url.path(), |(_, path_part)| path_part);
.map_or_else(|| path, |(_, path_part)| path_part);
Self {
platforms: None,
name: (!name.is_empty()).then_some(name).map(str::to_owned),
base_url,
base_url: url.into(),
}
}
}
Expand All @@ -305,7 +299,8 @@ impl Channel {
base_url: config
.channel_alias
.join(dir_name.as_ref())
.expect("name is not a valid Url"),
.expect("name is not a valid Url")
.into(),
name: (!name.is_empty()).then_some(name).map(str::to_owned),
}
}
Expand All @@ -329,14 +324,14 @@ impl Channel {
let url = Url::from_directory_path(path).expect("path is a valid url");
Self {
platforms: None,
base_url: url,
base_url: url.into(),
name: None,
}
}

/// Returns the name of the channel
pub fn name(&self) -> &str {
match self.base_url().scheme() {
match self.base_url.url().scheme() {
// The name of the channel is only defined for http and https channels.
// If the name is not defined we return the base url.
"https" | "http" => self
Expand All @@ -347,17 +342,9 @@ impl Channel {
}
}

/// Returns the base Url of the channel. This does not include the platform
/// part.
pub fn base_url(&self) -> &Url {
&self.base_url
}

/// Returns the Urls for the given platform
pub fn platform_url(&self, platform: Platform) -> Url {
self.base_url()
.join(&format!("{}/", platform.as_str())) // trailing slash is important here as this signifies a directory
.expect("platform is a valid url fragment")
self.base_url.platform_url(platform)
}

/// Returns the Urls for all the supported platforms of this package.
Expand All @@ -380,7 +367,7 @@ impl Channel {

/// Returns the canonical name of the channel
pub fn canonical_name(&self) -> String {
self.base_url.clone().redact().to_string()
self.base_url.url().clone().redact().to_string()
}
}

Expand Down Expand Up @@ -579,7 +566,7 @@ mod tests {

let channel = Channel::from_str("conda-forge", &config).unwrap();
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("https://conda.anaconda.org/conda-forge/").unwrap()
);
assert_eq!(channel.name.as_deref(), Some("conda-forge"));
Expand All @@ -596,14 +583,14 @@ mod tests {
let channel =
Channel::from_str("https://conda.anaconda.org/conda-forge/", &config).unwrap();
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("https://conda.anaconda.org/conda-forge/").unwrap()
);
assert_eq!(channel.name.as_deref(), Some("conda-forge"));
assert_eq!(channel.name(), "conda-forge");
assert_eq!(channel.platforms, None);
assert_eq!(
channel.base_url().to_string(),
channel.base_url.to_string(),
"https://conda.anaconda.org/conda-forge/"
);

Expand All @@ -622,12 +609,12 @@ mod tests {
assert_eq!(channel.name.as_deref(), Some("conda-forge"));
assert_eq!(channel.name(), "file:///var/channels/conda-forge/");
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("file:///var/channels/conda-forge/").unwrap()
);
assert_eq!(channel.platforms, None);
assert_eq!(
channel.base_url().to_string(),
channel.base_url.to_string(),
"file:///var/channels/conda-forge/"
);

Expand All @@ -643,7 +630,7 @@ mod tests {
);
assert_eq!(channel.platforms, None);
assert_eq!(
channel.base_url().to_file_path().unwrap(),
channel.base_url.url().to_file_path().unwrap(),
current_dir.join("dir/does/not_exist")
);
}
Expand All @@ -654,7 +641,7 @@ mod tests {

let channel = Channel::from_str("http://localhost:1234", &config).unwrap();
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("http://localhost:1234/").unwrap()
);
assert_eq!(channel.name, None);
Expand All @@ -681,7 +668,7 @@ mod tests {
)
.unwrap();
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("https://conda.anaconda.org/conda-forge/").unwrap()
);
assert_eq!(channel.name.as_deref(), Some("conda-forge"));
Expand All @@ -693,15 +680,15 @@ mod tests {
)
.unwrap();
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("https://conda.anaconda.org/pkgs/main/").unwrap()
);
assert_eq!(channel.name.as_deref(), Some("pkgs/main"));
assert_eq!(channel.platforms, Some(vec![platform]));

let channel = Channel::from_str("conda-forge/label/rust_dev", &config).unwrap();
assert_eq!(
channel.base_url,
channel.base_url.url().clone(),
Url::from_str("https://conda.anaconda.org/conda-forge/label/rust_dev/").unwrap()
);
assert_eq!(channel.name.as_deref(), Some("conda-forge/label/rust_dev"));
Expand Down Expand Up @@ -785,8 +772,8 @@ mod tests {

for channel_str in test_channels {
let channel = Channel::from_str(channel_str, &channel_config).unwrap();
assert!(channel.base_url().as_str().ends_with('/'));
assert!(!channel.base_url().as_str().ends_with("//"));
assert!(channel.base_url.as_str().ends_with('/'));
assert!(!channel.base_url.as_str().ends_with("//"));

let named_channel = NamedChannelOrUrl::from_str(channel_str).unwrap();
let base_url = named_channel
Expand All @@ -798,8 +785,8 @@ mod tests {
assert!(!base_url_str.ends_with("//"));

let channel = named_channel.into_channel(&channel_config).unwrap();
assert!(channel.base_url().as_str().ends_with('/'));
assert!(!channel.base_url().as_str().ends_with("//"));
assert!(channel.base_url.as_str().ends_with('/'));
assert!(!channel.base_url.as_str().ends_with("//"));
}
}

Expand All @@ -813,14 +800,14 @@ mod tests {
let channel = Channel::from_str("conda-forge", &channel_config).unwrap();
assert_eq!(
&channel.base_url,
named.into_channel(&channel_config).unwrap().base_url()
&named.into_channel(&channel_config).unwrap().base_url
);

let named = NamedChannelOrUrl::Name("nvidia/label/cuda-11.8.0".to_string());
let channel = Channel::from_str("nvidia/label/cuda-11.8.0", &channel_config).unwrap();
assert_eq!(
channel.base_url(),
named.into_channel(&channel_config).unwrap().base_url()
channel.base_url,
named.into_channel(&channel_config).unwrap().base_url
);
}
}
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod prefix_record;
use std::path::{Path, PathBuf};

pub use build_spec::{BuildNumber, BuildNumberSpec, ParseBuildNumberSpecError};
pub use channel::{Channel, ChannelConfig, NamedChannelOrUrl, ParseChannelError};
pub use channel::{Channel, ChannelConfig, CondaUrl, NamedChannelOrUrl, ParseChannelError};
pub use channel_data::{ChannelData, ChannelDataPackage};
pub use environment_yaml::{EnvironmentYaml, MatchSpecOrSubSection};
pub use explicit_environment_spec::{
Expand Down
Loading