mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add puffin venv
command to create virtual environments (#83)
Closes https://github.com/astral-sh/puffin/issues/58.
This commit is contained in:
parent
a0294a510c
commit
d0764bdc23
8 changed files with 76 additions and 28 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1773,6 +1773,7 @@ dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
"directories",
|
"directories",
|
||||||
"futures",
|
"futures",
|
||||||
|
"gourgeist",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"install-wheel-rs",
|
"install-wheel-rs",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
|
@ -1791,6 +1792,7 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-tree",
|
"tracing-tree",
|
||||||
"url",
|
"url",
|
||||||
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -23,7 +23,12 @@ pub struct InterpreterInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the interpreter.rs info, either cached or by running it.
|
/// 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 cache_dir = crate_cache_dir()?.join("interpreter_info");
|
||||||
|
|
||||||
let index = seahash::hash(interpreter.as_str().as_bytes());
|
let index = seahash::hash(interpreter.as_str().as_bytes());
|
||||||
|
|
|
@ -50,6 +50,8 @@ pub enum Error {
|
||||||
#[source]
|
#[source]
|
||||||
err: install_wheel_rs::Error,
|
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> {
|
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`.
|
/// Create a virtualenv and if not bare, install `wheel`, `pip` and `setuptools`.
|
||||||
pub fn create_venv(
|
pub fn create_venv(
|
||||||
location: &Utf8Path,
|
location: impl AsRef<std::path::Path>,
|
||||||
base_python: &Utf8Path,
|
base_python: impl AsRef<std::path::Path>,
|
||||||
info: &InterpreterInfo,
|
info: &InterpreterInfo,
|
||||||
bare: bool,
|
bare: bool,
|
||||||
) -> Result<(), Error> {
|
) -> 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)?;
|
let paths = create_bare_venv(location, base_python, info)?;
|
||||||
|
|
||||||
if !bare {
|
if !bare {
|
||||||
|
|
|
@ -25,7 +25,7 @@ fn run() -> Result<(), gourgeist::Error> {
|
||||||
let location = cli.path.unwrap_or(Utf8PathBuf::from(".venv"));
|
let location = cli.path.unwrap_or(Utf8PathBuf::from(".venv"));
|
||||||
let python = parse_python_cli(cli.python)?;
|
let python = parse_python_cli(cli.python)?;
|
||||||
let data = get_interpreter_info(&python)?;
|
let data = get_interpreter_info(&python)?;
|
||||||
create_venv(&location, &python, &data, cli.bare)?;
|
create_venv(location, &python, &data, cli.bare)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ name = "puffin"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
gourgeist = { path = "../gourgeist" }
|
||||||
install-wheel-rs = { path = "../install-wheel-rs", default-features = false }
|
install-wheel-rs = { path = "../install-wheel-rs", default-features = false }
|
||||||
pep440_rs = { path = "../pep440-rs" }
|
pep440_rs = { path = "../pep440-rs" }
|
||||||
pep508_rs = { path = "../pep508-rs" }
|
pep508_rs = { path = "../pep508-rs" }
|
||||||
|
@ -34,3 +35,4 @@ tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
tracing-tree = { workspace = true }
|
tracing-tree = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
which = { workspace = true}
|
||||||
|
|
|
@ -6,6 +6,7 @@ pub(crate) use compile::compile;
|
||||||
pub(crate) use freeze::freeze;
|
pub(crate) use freeze::freeze;
|
||||||
pub(crate) use sync::{sync, SyncFlags};
|
pub(crate) use sync::{sync, SyncFlags};
|
||||||
pub(crate) use uninstall::uninstall;
|
pub(crate) use uninstall::uninstall;
|
||||||
|
pub(crate) use venv::venv;
|
||||||
|
|
||||||
mod clean;
|
mod clean;
|
||||||
mod compile;
|
mod compile;
|
||||||
|
@ -13,6 +14,7 @@ mod freeze;
|
||||||
mod reporters;
|
mod reporters;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod uninstall;
|
mod uninstall;
|
||||||
|
mod venv;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum ExitStatus {
|
pub(crate) enum ExitStatus {
|
||||||
|
|
36
crates/puffin-cli/src/commands/venv.rs
Normal file
36
crates/puffin-cli/src/commands/venv.rs
Normal 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)
|
||||||
|
}
|
|
@ -25,6 +25,10 @@ struct Cli {
|
||||||
/// Use verbose output.
|
/// Use verbose output.
|
||||||
#[arg(global = true, long, short, conflicts_with = "quiet")]
|
#[arg(global = true, long, short, conflicts_with = "quiet")]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
|
||||||
|
/// Avoid reading from or writing to the cache.
|
||||||
|
#[arg(long)]
|
||||||
|
no_cache: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
|
@ -36,19 +40,17 @@ enum Commands {
|
||||||
/// Clear the cache.
|
/// Clear the cache.
|
||||||
Clean,
|
Clean,
|
||||||
/// Enumerate the installed packages in the current environment.
|
/// Enumerate the installed packages in the current environment.
|
||||||
Freeze(FreezeArgs),
|
Freeze,
|
||||||
/// Uninstall a package.
|
/// Uninstall a package.
|
||||||
Uninstall(UninstallArgs),
|
Uninstall(UninstallArgs),
|
||||||
|
/// Create a virtual environment.
|
||||||
|
Venv(VenvArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
struct CompileArgs {
|
struct CompileArgs {
|
||||||
/// Path to the `requirements.txt` file to compile.
|
/// Path to the `requirements.txt` file to compile.
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
|
|
||||||
/// Avoid reading from or writing to the cache.
|
|
||||||
#[arg(long)]
|
|
||||||
no_cache: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
@ -56,30 +58,21 @@ struct SyncArgs {
|
||||||
/// Path to the `requirements.txt` file to install.
|
/// Path to the `requirements.txt` file to install.
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
|
|
||||||
/// Avoid reading from or writing to the cache.
|
|
||||||
#[arg(long)]
|
|
||||||
no_cache: bool,
|
|
||||||
|
|
||||||
/// Ignore any installed packages, forcing a re-installation.
|
/// Ignore any installed packages, forcing a re-installation.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
ignore_installed: bool,
|
ignore_installed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
struct FreezeArgs {
|
|
||||||
/// Avoid reading from or writing to the cache.
|
|
||||||
#[arg(long)]
|
|
||||||
no_cache: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
struct UninstallArgs {
|
struct UninstallArgs {
|
||||||
/// The name of the package to uninstall.
|
/// The name of the package to uninstall.
|
||||||
name: String,
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Avoid reading from or writing to the cache.
|
#[derive(Args)]
|
||||||
#[arg(long)]
|
struct VenvArgs {
|
||||||
no_cache: bool,
|
/// The path to the virtual environment to create.
|
||||||
|
name: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -108,7 +101,7 @@ async fn main() -> ExitCode {
|
||||||
&args.src,
|
&args.src,
|
||||||
dirs.as_ref()
|
dirs.as_ref()
|
||||||
.map(ProjectDirs::cache_dir)
|
.map(ProjectDirs::cache_dir)
|
||||||
.filter(|_| !args.no_cache),
|
.filter(|_| !cli.no_cache),
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -118,7 +111,7 @@ async fn main() -> ExitCode {
|
||||||
&args.src,
|
&args.src,
|
||||||
dirs.as_ref()
|
dirs.as_ref()
|
||||||
.map(ProjectDirs::cache_dir)
|
.map(ProjectDirs::cache_dir)
|
||||||
.filter(|_| !args.no_cache),
|
.filter(|_| !cli.no_cache),
|
||||||
if args.ignore_installed {
|
if args.ignore_installed {
|
||||||
commands::SyncFlags::IGNORE_INSTALLED
|
commands::SyncFlags::IGNORE_INSTALLED
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,11 +124,11 @@ async fn main() -> ExitCode {
|
||||||
Commands::Clean => {
|
Commands::Clean => {
|
||||||
commands::clean(dirs.as_ref().map(ProjectDirs::cache_dir), printer).await
|
commands::clean(dirs.as_ref().map(ProjectDirs::cache_dir), printer).await
|
||||||
}
|
}
|
||||||
Commands::Freeze(args) => {
|
Commands::Freeze => {
|
||||||
commands::freeze(
|
commands::freeze(
|
||||||
dirs.as_ref()
|
dirs.as_ref()
|
||||||
.map(ProjectDirs::cache_dir)
|
.map(ProjectDirs::cache_dir)
|
||||||
.filter(|_| !args.no_cache),
|
.filter(|_| !cli.no_cache),
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -145,11 +138,12 @@ async fn main() -> ExitCode {
|
||||||
&args.name,
|
&args.name,
|
||||||
dirs.as_ref()
|
dirs.as_ref()
|
||||||
.map(ProjectDirs::cache_dir)
|
.map(ProjectDirs::cache_dir)
|
||||||
.filter(|_| !args.no_cache),
|
.filter(|_| !cli.no_cache),
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
Commands::Venv(args) => commands::venv(&args.name, printer).await,
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue