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: channel add command #254

Closed
wants to merge 2 commits into from
Closed
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
77 changes: 77 additions & 0 deletions src/cli/channel/add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::environment::{load_lock_file, update_lock_file, update_prefix};
use crate::prefix::Prefix;
use crate::Project;
use clap::Parser;
use itertools::Itertools;
use miette::IntoDiagnostic;
use rattler_conda_types::{Channel, ChannelConfig, Platform};

/// Adds a channel to the project
#[derive(Parser, Debug, Default)]
pub struct Args {
/// The channel name or URL
#[clap(required = true, num_args=1..)]
pub channel: Vec<String>,

/// Don't update the environment, only add changed packages to the lock-file.
#[clap(long)]
pub no_install: bool,
}

pub async fn execute(mut project: Project, args: Args) -> miette::Result<()> {
// Determine which channels are missing
let channel_config = ChannelConfig::default();
let channels = args
.channel
.into_iter()
.map(|channel_str| {
Channel::from_str(&channel_str, &channel_config).map(|channel| (channel_str, channel))
})
.collect::<Result<Vec<_>, _>>()
.into_diagnostic()?;

let missing_channels = channels
.into_iter()
.filter(|(_name, channel)| !project.channels().contains(channel))
.collect_vec();

if missing_channels.is_empty() {
eprintln!(
"{}All channel(s) have already been added.",
console::style(console::Emoji("✔ ", "")).green(),
);
return Ok(());
}

// Load the existing lock-file
let lock_file = load_lock_file(&project).await?;

// Add the channels to the lock-file
project.add_channels(missing_channels.iter().map(|(name, _channel)| name))?;

// Try to update the lock-file with the new channels
let lock_file = update_lock_file(&project, lock_file, None).await?;
project.save()?;

// Update the installation if needed
if !args.no_install {
// Get the currently installed packages
let prefix = Prefix::new(project.root().join(".pixi/env"))?;
let installed_packages = prefix.find_installed_packages(None).await?;

// Update the prefix
update_prefix(&prefix, installed_packages, &lock_file, Platform::current()).await?;
}

// Report back to the user
for (name, channel) in missing_channels {
eprintln!(
"{}Added {} ({})",
console::style(console::Emoji("✔ ", "")).green(),
name,
channel.base_url()
);
}

Ok(())
}
30 changes: 30 additions & 0 deletions src/cli/channel/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pub mod add;

use crate::Project;
use clap::Parser;
use std::path::PathBuf;

/// Commands to manage project channels.
#[derive(Parser, Debug)]
pub struct Args {
/// The path to 'pixi.toml'
#[clap(long, global = true)]
pub manifest_path: Option<PathBuf>,

/// The subcommand to execute
#[clap(subcommand)]
pub command: Command,
}

#[derive(Parser, Debug)]
pub enum Command {
Add(add::Args),
}

pub async fn execute(args: Args) -> miette::Result<()> {
let project = Project::load_or_else_discover(args.manifest_path.as_deref())?;

match args.command {
Command::Add(args) => add::execute(project, args).await,
}
}
3 changes: 3 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use tracing_subscriber::{filter::LevelFilter, util::SubscriberInitExt, EnvFilter

pub mod add;
pub mod auth;
pub mod channel;
pub mod global;
pub mod info;
pub mod init;
Expand Down Expand Up @@ -63,6 +64,7 @@ pub enum Command {
Task(task::Args),
Info(info::Args),
Upload(upload::Args),
Channel(channel::Args),
}

fn completion(args: CompletionCommand) -> miette::Result<()> {
Expand Down Expand Up @@ -165,6 +167,7 @@ pub async fn execute_command(command: Command) -> miette::Result<()> {
Command::Task(cmd) => task::execute(cmd),
Command::Info(cmd) => info::execute(cmd).await,
Command::Upload(cmd) => upload::execute(cmd).await,
Command::Channel(cmd) => channel::execute(cmd).await,
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/repodata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub async fn fetch_sparse_repodata(
&repodata_cache,
download_client,
progress_bar.clone(),
platform == Platform::NoArch,
platform != Platform::NoArch,
)
.await;

Expand Down
49 changes: 49 additions & 0 deletions tests/channel_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
mod common;

use crate::{common::package_database::PackageDatabase, common::PixiControl};
use rattler_conda_types::{Channel, ChannelConfig};
use tempfile::TempDir;
use url::Url;

#[tokio::test]
async fn add_channel() {
// Create a local package database with no entries and write it to disk. This ensures that we
// have a valid channel.
let package_database = PackageDatabase::default();
let initial_channel_dir = TempDir::new().unwrap();
package_database
.write_repodata(initial_channel_dir.path())
.await
.unwrap();

// Run the init command
let pixi = PixiControl::new().unwrap();
pixi.init()
.with_local_channel(initial_channel_dir.path())
.await
.unwrap();

// Create and add another local package directory
let additional_channel_dir = TempDir::new().unwrap();
package_database
.write_repodata(additional_channel_dir.path())
.await
.unwrap();
pixi.channel_add()
.with_local_channel(additional_channel_dir.path())
.await
.unwrap();

// There should be a loadable project manifest in the directory
let project = pixi.project().unwrap();

// Our channel should be in the list of channels
let local_channel = Channel::from_str(
Url::from_directory_path(additional_channel_dir.path())
.unwrap()
.to_string(),
&ChannelConfig::default(),
)
.unwrap();
assert!(project.channels().contains(&local_channel));
}
32 changes: 31 additions & 1 deletion tests/common/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
//! ```

use crate::common::IntoMatchSpec;
use pixi::cli::{add, init, task};
use pixi::cli::{add, channel, init, task};
use pixi::project::SpecType;
use rattler_conda_types::Platform;
use std::future::{Future, IntoFuture};
Expand Down Expand Up @@ -163,3 +163,33 @@ impl TaskAliasBuilder {
})
}
}

pub struct ChannelAddBuilder {
pub manifest_path: Option<PathBuf>,
pub args: channel::add::Args,
}

impl ChannelAddBuilder {
/// Adds the specified channel
pub fn with_channel(mut self, name: impl Into<String>) -> Self {
self.args.channel.push(name.into());
self
}

/// Alias to add a local channel.
pub fn with_local_channel(self, channel: impl AsRef<Path>) -> Self {
self.with_channel(Url::from_directory_path(channel).unwrap())
}
}

impl IntoFuture for ChannelAddBuilder {
type Output = miette::Result<()>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'static>>;

fn into_future(self) -> Self::IntoFuture {
Box::pin(channel::execute(channel::Args {
manifest_path: self.manifest_path,
command: channel::Command::Add(self.args),
}))
}
}
17 changes: 15 additions & 2 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
pub mod builders;
pub mod package_database;

use crate::common::builders::{AddBuilder, InitBuilder, TaskAddBuilder, TaskAliasBuilder};
use crate::common::builders::{
AddBuilder, ChannelAddBuilder, InitBuilder, TaskAddBuilder, TaskAliasBuilder,
};
use pixi::cli::install::Args;
use pixi::cli::run::{
create_script, execute_script_with_output, get_task_env, order_tasks, RunOutput,
};
use pixi::cli::task::{AddArgs, AliasArgs};
use pixi::cli::{add, init, run, task};
use pixi::cli::{add, channel, init, run, task};
use pixi::{consts, Project};
use rattler_conda_types::conda_lock::CondaLock;
use rattler_conda_types::{MatchSpec, Platform, Version};
Expand Down Expand Up @@ -148,6 +150,17 @@ impl PixiControl {
}
}

/// Add a new channel to the project.
pub fn channel_add(&self) -> ChannelAddBuilder {
ChannelAddBuilder {
manifest_path: Some(self.manifest_path()),
args: channel::add::Args {
channel: vec![],
no_install: true,
},
}
}

/// Run a command
pub async fn run(&self, mut args: run::Args) -> miette::Result<RunOutput> {
args.manifest_path = args.manifest_path.or_else(|| Some(self.manifest_path()));
Expand Down