Add puffin venv command to create virtual environments (#83)

Closes https://github.com/astral-sh/puffin/issues/58.
This commit is contained in:
Charlie Marsh 2023-10-10 13:46:25 -04:00 committed by GitHub
parent a0294a510c
commit d0764bdc23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 76 additions and 28 deletions

2
Cargo.lock generated
View file

@ -1773,6 +1773,7 @@ dependencies = [
"colored",
"directories",
"futures",
"gourgeist",
"indicatif",
"install-wheel-rs",
"itertools 0.11.0",
@ -1791,6 +1792,7 @@ dependencies = [
"tracing-subscriber",
"tracing-tree",
"url",
"which",
]
[[package]]

View file

@ -23,7 +23,12 @@ pub struct InterpreterInfo {
}
/// Gets the interpreter.rs info, either cached or by running it.
pub fn get_interpreter_info(interpreter: &Utf8Path) -> Result<InterpreterInfo, Error> {
pub fn get_interpreter_info(
interpreter: impl AsRef<std::path::Path>,
) -> Result<InterpreterInfo, Error> {
let interpreter = Utf8Path::from_path(interpreter.as_ref())
.ok_or_else(|| Error::NonUTF8Path(interpreter.as_ref().to_path_buf()))?;
let cache_dir = crate_cache_dir()?.join("interpreter_info");
let index = seahash::hash(interpreter.as_str().as_bytes());

View file

@ -50,6 +50,8 @@ pub enum Error {
#[source]
err: install_wheel_rs::Error,
},
#[error("{0} is not a valid UTF-8 path")]
NonUTF8Path(std::path::PathBuf),
}
pub(crate) fn crate_cache_dir() -> io::Result<Utf8PathBuf> {
@ -61,11 +63,16 @@ pub(crate) fn crate_cache_dir() -> io::Result<Utf8PathBuf> {
/// Create a virtualenv and if not bare, install `wheel`, `pip` and `setuptools`.
pub fn create_venv(
location: &Utf8Path,
base_python: &Utf8Path,
location: impl AsRef<std::path::Path>,
base_python: impl AsRef<std::path::Path>,
info: &InterpreterInfo,
bare: bool,
) -> Result<(), Error> {
let location = Utf8Path::from_path(location.as_ref())
.ok_or_else(|| Error::NonUTF8Path(location.as_ref().to_path_buf()))?;
let base_python = Utf8Path::from_path(base_python.as_ref())
.ok_or_else(|| Error::NonUTF8Path(base_python.as_ref().to_path_buf()))?;
let paths = create_bare_venv(location, base_python, info)?;
if !bare {

View file

@ -25,7 +25,7 @@ fn run() -> Result<(), gourgeist::Error> {
let location = cli.path.unwrap_or(Utf8PathBuf::from(".venv"));
let python = parse_python_cli(cli.python)?;
let data = get_interpreter_info(&python)?;
create_venv(&location, &python, &data, cli.bare)?;
create_venv(location, &python, &data, cli.bare)?;
Ok(())
}

View file

@ -8,6 +8,7 @@ name = "puffin"
path = "src/main.rs"
[dependencies]
gourgeist = { path = "../gourgeist" }
install-wheel-rs = { path = "../install-wheel-rs", default-features = false }
pep440_rs = { path = "../pep440-rs" }
pep508_rs = { path = "../pep508-rs" }
@ -34,3 +35,4 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-tree = { workspace = true }
url = { workspace = true }
which = { workspace = true}

View file

@ -6,6 +6,7 @@ pub(crate) use compile::compile;
pub(crate) use freeze::freeze;
pub(crate) use sync::{sync, SyncFlags};
pub(crate) use uninstall::uninstall;
pub(crate) use venv::venv;
mod clean;
mod compile;
@ -13,6 +14,7 @@ mod freeze;
mod reporters;
mod sync;
mod uninstall;
mod venv;
#[derive(Copy, Clone)]
pub(crate) enum ExitStatus {

View file

@ -0,0 +1,36 @@
use std::fmt::Write;
use std::path::Path;
use anyhow::Result;
use colored::Colorize;
use crate::commands::ExitStatus;
use crate::printer::Printer;
/// Create a virtual environment.
pub(crate) async fn venv(path: &Path, mut printer: Printer) -> Result<ExitStatus> {
// Locate the Python interpreter.
// TODO(charlie): Look at how Maturin discovers and ranks all the available Python interpreters.
let executable = which::which("python3").or_else(|_| which::which("python"))?;
let interpreter_info = gourgeist::get_interpreter_info(&executable)?;
writeln!(
printer,
"Using Python interpreter: {}",
format!("{}", executable.display()).cyan()
)?;
// If the path already exists, remove it.
tokio::fs::remove_file(path).await.ok();
tokio::fs::remove_dir_all(path).await.ok();
writeln!(
printer,
"Creating virtual environment at: {}",
format!("{}", path.display()).cyan()
)?;
// Create the virtual environment.
gourgeist::create_venv(path, &executable, &interpreter_info, true)?;
Ok(ExitStatus::Success)
}

View file

@ -25,6 +25,10 @@ struct Cli {
/// Use verbose output.
#[arg(global = true, long, short, conflicts_with = "quiet")]
verbose: bool,
/// Avoid reading from or writing to the cache.
#[arg(long)]
no_cache: bool,
}
#[derive(Subcommand)]
@ -36,19 +40,17 @@ enum Commands {
/// Clear the cache.
Clean,
/// Enumerate the installed packages in the current environment.
Freeze(FreezeArgs),
Freeze,
/// Uninstall a package.
Uninstall(UninstallArgs),
/// Create a virtual environment.
Venv(VenvArgs),
}
#[derive(Args)]
struct CompileArgs {
/// Path to the `requirements.txt` file to compile.
src: PathBuf,
/// Avoid reading from or writing to the cache.
#[arg(long)]
no_cache: bool,
}
#[derive(Args)]
@ -56,30 +58,21 @@ struct SyncArgs {
/// Path to the `requirements.txt` file to install.
src: PathBuf,
/// Avoid reading from or writing to the cache.
#[arg(long)]
no_cache: bool,
/// Ignore any installed packages, forcing a re-installation.
#[arg(long)]
ignore_installed: bool,
}
#[derive(Args)]
struct FreezeArgs {
/// Avoid reading from or writing to the cache.
#[arg(long)]
no_cache: bool,
}
#[derive(Args)]
struct UninstallArgs {
/// The name of the package to uninstall.
name: String,
}
/// Avoid reading from or writing to the cache.
#[arg(long)]
no_cache: bool,
#[derive(Args)]
struct VenvArgs {
/// The path to the virtual environment to create.
name: PathBuf,
}
#[tokio::main]
@ -108,7 +101,7 @@ async fn main() -> ExitCode {
&args.src,
dirs.as_ref()
.map(ProjectDirs::cache_dir)
.filter(|_| !args.no_cache),
.filter(|_| !cli.no_cache),
printer,
)
.await
@ -118,7 +111,7 @@ async fn main() -> ExitCode {
&args.src,
dirs.as_ref()
.map(ProjectDirs::cache_dir)
.filter(|_| !args.no_cache),
.filter(|_| !cli.no_cache),
if args.ignore_installed {
commands::SyncFlags::IGNORE_INSTALLED
} else {
@ -131,11 +124,11 @@ async fn main() -> ExitCode {
Commands::Clean => {
commands::clean(dirs.as_ref().map(ProjectDirs::cache_dir), printer).await
}
Commands::Freeze(args) => {
Commands::Freeze => {
commands::freeze(
dirs.as_ref()
.map(ProjectDirs::cache_dir)
.filter(|_| !args.no_cache),
.filter(|_| !cli.no_cache),
printer,
)
.await
@ -145,11 +138,12 @@ async fn main() -> ExitCode {
&args.name,
dirs.as_ref()
.map(ProjectDirs::cache_dir)
.filter(|_| !args.no_cache),
.filter(|_| !cli.no_cache),
printer,
)
.await
}
Commands::Venv(args) => commands::venv(&args.name, printer).await,
};
match result {