Skip to content

Commit

Permalink
Merge branch 'allow-insecure-host' of github.com:zen-xu/pixi into all…
Browse files Browse the repository at this point in the history
…ow-insecure-host
  • Loading branch information
zen-xu committed Nov 20, 2024
2 parents e086c4f + 28c4281 commit 082f65d
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 58 deletions.
7 changes: 5 additions & 2 deletions crates/pixi_config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,10 +676,13 @@ impl Config {
let mut config = Self::load_system();

for p in config_path_global() {
if !p.is_file() {
continue;
}
match Self::from_path(&p) {
Ok(c) => config = config.merge_config(c),
Err(e) => tracing::debug!(
"Failed to load global config: {} (error: {})",
Err(e) => tracing::warn!(
"Failed to load global config '{}' with error: {}",
p.display(),
e
),
Expand Down
1 change: 1 addition & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ release = "python scripts/release.py"
run-all-examples = { cmd = "python tests/scripts/run-all-examples.py --pixi-exec $CARGO_TARGET_DIR/release/pixi", depends-on = [
"build-release",
] }
test = { depends-on = ["test-all-fast"] }
test-all-fast = { depends-on = ["test-fast", "test-integration-fast"] }
test-all-slow = { depends-on = ["test-slow", "test-integration-slow"] }
test-fast = "cargo nextest run --workspace"
Expand Down
180 changes: 146 additions & 34 deletions src/cli/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::io::{self, Write};
use std::str::FromStr;

use clap::Parser;
use indexmap::IndexMap;
use itertools::Itertools;
use miette::IntoDiagnostic;
use pixi_config::default_channel_config;
Expand Down Expand Up @@ -54,6 +55,7 @@ async fn search_package_by_filter<F, QF, FR>(
all_package_names: Vec<PackageName>,
repodata_query_func: QF,
filter_func: F,
only_latest: bool,
) -> miette::Result<Vec<RepoDataRecord>>
where
F: Fn(&PackageName, &PackageName) -> bool,
Expand All @@ -76,32 +78,38 @@ where

let repos: Vec<RepoData> = repodata_query_func(specs).await.into_diagnostic()?;

let mut latest_packages: Vec<RepoDataRecord> = Vec::new();

for repo in repos {
// sort records by version, get the latest one of each package
let records_of_repo: HashMap<String, RepoDataRecord> = repo
.into_iter()
.sorted_by(|a, b| a.package_record.version.cmp(&b.package_record.version))
.map(|record| {
(
record.package_record.name.as_normalized().to_string(),
record.clone(),
)
})
.collect();

latest_packages.extend(records_of_repo.into_values().collect_vec());
let mut packages: Vec<RepoDataRecord> = Vec::new();
if only_latest {
for repo in repos {
// sort records by version, get the latest one of each package
let records_of_repo: HashMap<String, RepoDataRecord> = repo
.into_iter()
.sorted_by(|a, b| a.package_record.version.cmp(&b.package_record.version))
.map(|record| {
(
record.package_record.name.as_normalized().to_string(),
record.clone(),
)
})
.collect();

packages.extend(records_of_repo.into_values().collect_vec());
}
// sort all versions across all channels and platforms
packages.sort_by(|a, b| a.package_record.version.cmp(&b.package_record.version));
} else {
for repo in repos {
packages.extend(repo.into_iter().cloned().collect_vec());
}
}

// sort all versions across all channels and platforms
latest_packages.sort_by(|a, b| a.package_record.version.cmp(&b.package_record.version));

Ok(latest_packages)
Ok(packages)
}

pub async fn execute_impl(args: Args) -> miette::Result<Option<Vec<RepoDataRecord>>> {
let stdout = io::stdout();
pub async fn execute_impl<W: Write>(
args: Args,
out: &mut W,
) -> miette::Result<Option<Vec<RepoDataRecord>>> {
let project = Project::load_or_else_discover(args.project_config.manifest_path.as_deref()).ok();

// Resolve channels from project / CLI args
Expand Down Expand Up @@ -156,7 +164,7 @@ pub async fn execute_impl(args: Args) -> miette::Result<Option<Vec<RepoDataRecor
all_names,
repodata_query_func,
args.limit,
stdout,
out,
)
.await?
}
Expand All @@ -165,23 +173,24 @@ pub async fn execute_impl(args: Args) -> miette::Result<Option<Vec<RepoDataRecor
else {
let package_name = PackageName::try_from(package_name_filter).into_diagnostic()?;

search_exact_package(package_name, all_names, repodata_query_func, stdout).await?
search_exact_package(package_name, all_names, repodata_query_func, out).await?
};

Project::warn_on_discovered_from_env(args.project_config.manifest_path.as_deref());
Ok(packages)
}

pub async fn execute(args: Args) -> miette::Result<()> {
execute_impl(args).await?;
let mut out = io::stdout();
execute_impl(args, &mut out).await?;
Ok(())
}

async fn search_exact_package<W: Write, QF, FR>(
package_name: PackageName,
all_repodata_names: Vec<PackageName>,
repodata_query_func: QF,
out: W,
out: &mut W,
) -> miette::Result<Option<Vec<RepoDataRecord>>>
where
QF: Fn(Vec<MatchSpec>) -> FR,
Expand All @@ -193,33 +202,85 @@ where
all_repodata_names,
repodata_query_func,
|pn, n| pn == n,
false,
)
.await?;
// Sort packages by version, build number and build string
let packages = packages
.iter()
.sorted_by(|a, b| {
Ord::cmp(
&(
&a.package_record.version,
a.package_record.build_number,
&a.package_record.build,
),
&(
&b.package_record.version,
b.package_record.build_number,
&b.package_record.build,
),
)
})
.cloned()
.collect::<Vec<RepoDataRecord>>();

if packages.is_empty() {
let normalized_package_name = package_name.as_normalized();
return Err(miette::miette!("Package {normalized_package_name} not found, please use a wildcard '*' in the search name for a broader result."));
}

let package = packages.last();
if let Some(package) = package {
if let Err(e) = print_package_info(package, out) {
let newest_package = packages.last();
if let Some(newest_package) = newest_package {
let other_versions = packages
.iter()
.filter(|p| p.package_record != newest_package.package_record)
.collect::<Vec<_>>();
if let Err(e) = print_package_info(newest_package, &other_versions, out) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
return Err(e).into_diagnostic();
}
}
}

Ok(package.map(|package| vec![package.clone()]))
Ok(newest_package.map(|package| vec![package.clone()]))
}

fn format_additional_builds_string(builds: Option<Vec<&RepoDataRecord>>) -> String {
let builds = builds.unwrap_or_default();
match builds.len() {
0 => String::new(),
1 => " (+ 1 build)".to_string(),
_ => format!(" (+ {} builds)", builds.len()),
}
}

fn print_package_info<W: Write>(package: &RepoDataRecord, mut out: W) -> io::Result<()> {
fn print_package_info<W: Write>(
package: &RepoDataRecord,
other_versions: &Vec<&RepoDataRecord>,
out: &mut W,
) -> io::Result<()> {
writeln!(out)?;

let package = package.clone();
let package_name = package.package_record.name.as_source();
let build = &package.package_record.build;
let package_info = format!("{} {}", console::style(package_name), console::style(build));
let mut grouped_by_version = IndexMap::new();
for version in other_versions {
grouped_by_version
.entry(&version.package_record.version)
.or_insert_with(Vec::new)
.insert(0, *version);
}
let other_builds = grouped_by_version.shift_remove(&package.package_record.version);
let package_info = format!(
"{}-{}-{}{}",
console::style(package.package_record.name.as_source()),
console::style(package.package_record.version.to_string()),
console::style(&package.package_record.build),
console::style(format_additional_builds_string(other_builds))
);

writeln!(out, "{}", package_info)?;
writeln!(out, "{}\n", "-".repeat(package_info.chars().count()))?;

Expand Down Expand Up @@ -314,6 +375,55 @@ fn print_package_info<W: Write>(package: &RepoDataRecord, mut out: W) -> io::Res
writeln!(out, " - {}", dependency)?;
}

// Print summary of older versions for package
if !grouped_by_version.is_empty() {
writeln!(out, "\nOther Versions ({}):", grouped_by_version.len())?;
let version_width = grouped_by_version
.keys()
.map(|v| v.to_string().len())
.chain(["Version".len()].iter().cloned())
.max()
.unwrap()
+ 1;
let build_width = other_versions
.iter()
.map(|v| v.package_record.build.len())
.chain(["Build".len()].iter().cloned())
.max()
.unwrap()
+ 1;
writeln!(
out,
"{:version_width$} {:build_width$}",
console::style("Version").bold(),
console::style("Build").bold(),
version_width = version_width,
build_width = build_width
)?;
let max_displayed_versions = 4;
let mut counter = 0;
for (version, builds) in grouped_by_version.iter().rev() {
writeln!(
out,
"{:version_width$} {:build_width$}{}",
console::style(version.to_string()),
console::style(builds[0].package_record.build.clone()),
console::style(format_additional_builds_string(Some(builds[1..].to_vec()))).dim(),
version_width = version_width,
build_width = build_width
)?;
counter += 1;
if counter == max_displayed_versions {
writeln!(
out,
"... and {} more",
grouped_by_version.len() - max_displayed_versions
)?;
break;
}
}
}

Ok(())
}

Expand All @@ -323,7 +433,7 @@ async fn search_package_by_wildcard<W: Write, QF, FR>(
all_package_names: Vec<PackageName>,
repodata_query_func: QF,
limit: Option<usize>,
out: W,
out: &mut W,
) -> miette::Result<Option<Vec<RepoDataRecord>>>
where
QF: Fn(Vec<MatchSpec>) -> FR + Clone,
Expand All @@ -340,6 +450,7 @@ where
all_package_names.clone(),
repodata_query_func.clone(),
|pn, _| wildcard_pattern.is_match(pn.as_normalized()),
true,
)
.await?;

Expand All @@ -354,6 +465,7 @@ where
all_package_names,
repodata_query_func,
|pn, n| jaro(pn.as_normalized(), n.as_normalized()) > similarity,
true,
)
.await
})
Expand Down Expand Up @@ -391,7 +503,7 @@ where

fn print_matching_packages<W: Write>(
packages: &[RepoDataRecord],
mut out: W,
out: &mut W,
limit: Option<usize>,
) -> io::Result<()> {
writeln!(
Expand Down
13 changes: 12 additions & 1 deletion src/global/project/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ mod tests {
use indexmap::IndexSet;
use insta::assert_snapshot;
use itertools::Itertools;
use pixi_consts::consts::DEFAULT_CHANNELS;
use rattler_conda_types::ParseStrictness;

use super::*;
Expand Down Expand Up @@ -769,7 +770,17 @@ mod tests {
MatchSpec::from_str("pythonic ==3.15.0", ParseStrictness::Strict).unwrap();

// Add environment
manifest.add_environment(&env_name, None).unwrap();
manifest
.add_environment(
&env_name,
Some(
DEFAULT_CHANNELS
.iter()
.map(|&name| NamedChannelOrUrl::Name(name.to_string()))
.collect(),
),
)
.unwrap();

// Add dependency
manifest
Expand Down
21 changes: 21 additions & 0 deletions tests/integration_python/test_main_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ def test_project_commands(pixi: Path, tmp_path: Path) -> None:
)


def test_broken_config(pixi: Path, tmp_path: Path) -> None:
env = {"PIXI_HOME": str(tmp_path)}
config = tmp_path.joinpath("config.toml")
toml = """
[repodata-config."https://prefix.dev/"]
disable-sharded = false
"""
config.write_text(toml)

# Test basic commands
verify_cli_command([pixi, "info"], env=env)

toml_invalid = toml + "invalid"
config.write_text(toml_invalid)
verify_cli_command([pixi, "info"], env=env, stderr_contains="parse error")

toml_unknown = "invalid-key = true" + toml
config.write_text(toml_unknown)
verify_cli_command([pixi, "info"], env=env, stderr_contains="Ignoring 'invalid-key'")


@pytest.mark.slow
def test_search(pixi: Path) -> None:
verify_cli_command(
Expand Down
6 changes: 5 additions & 1 deletion tests/integration_rust/common/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use pixi::cli::cli_config::{PrefixUpdateConfig, ProjectConfig};
use std::{
future::{Future, IntoFuture},
io,
path::{Path, PathBuf},
pin::Pin,
str::FromStr,
Expand Down Expand Up @@ -232,7 +233,10 @@ impl IntoFuture for SearchBuilder {
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + 'static>>;

fn into_future(self) -> Self::IntoFuture {
search::execute_impl(self.args).boxed_local()
Box::pin(async move {
let mut out = io::stdout();
search::execute_impl(self.args, &mut out).await
})
}
}

Expand Down
Loading

0 comments on commit 082f65d

Please sign in to comment.