Skip to content

Commit

Permalink
feat: add channel add feature on project subcommand. (#347)
Browse files Browse the repository at this point in the history
Added the #254 but with the `project` prefix. So the main cli entry
point stays as clean as possible. This is beneficial for future
expansion.
  • Loading branch information
ruben-arts committed Sep 20, 2023
1 parent a09f4b9 commit 4943582
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 4 deletions.
28 changes: 27 additions & 1 deletion docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,30 @@ pixi global install "python [version='3.11.0', build=he550d4f_1_cpython]"
pixi global install python=3.11.0=h10a6764_1_cpython
```

After using global install you can use the package you installed anywhere on your system.
After using global install, you can use the package you installed anywhere on your system.

## `project`

This subcommand allows you to modify the project configuration through the command line interface.

#### Options

- `--manifest-path`: the path to `pixi.toml`, by default it searches for one in the parent directories.
- `--no-install`: do not update the environment, only add changed packages to the lock-file.

### `project channel add`

Add channels to the channel list in the project configuration.
When you add channels, the channels are tested for existence, added to the lockfile and the environment is reinstalled.

#### Options

- `--no-install`: do not update the environment, only add changed packages to the lock-file.

```
pixi project channel add robostack
pixi project channel add bioconda conda-forge robostack
pixi project channel add file:///home/user/local_channel
pixi project channel add https://repo.prefix.dev/conda-forge
pixi project channel add --no-install robostack
```
3 changes: 3 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod global;
pub mod info;
pub mod init;
pub mod install;
pub mod project;
pub mod run;
pub mod search;
pub mod shell;
Expand Down Expand Up @@ -65,6 +66,7 @@ pub enum Command {
Info(info::Args),
Upload(upload::Args),
Search(search::Args),
Project(project::Args),
}

fn completion(args: CompletionCommand) -> miette::Result<()> {
Expand Down Expand Up @@ -168,6 +170,7 @@ pub async fn execute_command(command: Command) -> miette::Result<()> {
Command::Info(cmd) => info::execute(cmd).await,
Command::Upload(cmd) => upload::execute(cmd).await,
Command::Search(cmd) => search::execute(cmd).await,
Command::Project(cmd) => project::execute(cmd).await,
}
}

Expand Down
77 changes: 77 additions & 0 deletions src/cli/project/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 file and updates the lockfile.
#[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/project/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,
}
}
25 changes: 25 additions & 0 deletions src/cli/project/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use clap::Parser;
use std::path::PathBuf;

pub mod channel;

#[derive(Debug, Parser)]
pub enum Command {
Channel(channel::Args),
}
// Modify the project configuration file through the command line.
#[derive(Debug, Parser)]
pub struct Args {
#[command(subcommand)]
command: Command,
/// The path to 'pixi.toml'
#[arg(long)]
pub manifest_path: Option<PathBuf>,
}

pub async fn execute(cmd: Args) -> miette::Result<()> {
match cmd.command {
Command::Channel(args) => channel::execute(args).await?,
};
Ok(())
}
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, init, project, task};
use pixi::project::SpecType;
use rattler_conda_types::Platform;
use std::future::{Future, IntoFuture};
Expand Down Expand Up @@ -171,3 +171,33 @@ impl TaskAliasBuilder {
})
}
}

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

impl ProjectChannelAddBuilder {
/// 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 ProjectChannelAddBuilder {
type Output = miette::Result<()>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'static>>;

fn into_future(self) -> Self::IntoFuture {
Box::pin(project::channel::execute(project::channel::Args {
manifest_path: self.manifest_path,
command: project::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, InitBuilder, ProjectChannelAddBuilder, 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, init, project, run, task};
use pixi::{consts, Project};
use rattler_conda_types::conda_lock::CondaLock;
use rattler_conda_types::{MatchSpec, PackageName, Platform, Version};
Expand Down Expand Up @@ -161,6 +163,17 @@ impl PixiControl {
}
}

/// Add a new channel to the project.
pub fn project_channel_add(&self) -> ProjectChannelAddBuilder {
ProjectChannelAddBuilder {
manifest_path: Some(self.manifest_path()),
args: project::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
49 changes: 49 additions & 0 deletions tests/project_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.project_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));
}

0 comments on commit 4943582

Please sign in to comment.