use std::path::PathBuf;
use crate::cmd::*;
use crate::dependencies::FrozenDependency;
use crate::error::*;
use crate::operations;
use crate::paths::{Paths, PathsResolver};
use crate::python_info::PythonInfo;
use crate::run::VenvRunner;
use crate::settings::Settings;
pub struct Metadata {
pub dmenv_version: String,
pub python_platform: String,
pub python_version: String,
}
pub struct Project {
python_info: PythonInfo,
settings: Settings,
paths: Paths,
venv_runner: VenvRunner,
}
pub enum PostInstallAction {
RunSetupPyDevelop,
None,
}
impl Project {
pub fn new(
project_path: PathBuf,
python_info: PythonInfo,
settings: Settings,
) -> Result<Self, Error> {
let python_version = python_info.version.clone();
let paths_resolver = PathsResolver::new(project_path.clone(), &python_version, &settings);
let paths = paths_resolver.paths()?;
let venv_runner = VenvRunner::new(&project_path, &paths.venv);
Ok(Project {
python_info,
settings,
paths,
venv_runner,
})
}
pub fn use_system_site_packages(&mut self) {
self.settings.system_site_packages = true;
}
pub fn init(&self, options: &operations::InitOptions) -> Result<(), Error> {
operations::init(&self.paths.project, options)
}
pub fn clean(&self) -> Result<(), Error> {
operations::venv::clean(self.paths.venv.clone())
}
pub fn develop(&self) -> Result<(), Error> {
print_info_2("Running setup_py.py develop");
if !self.paths.setup_py.exists() {
return Err(Error::MissingSetupPy {});
}
self.venv_runner
.run(&["python", "setup.py", "develop", "--no-deps"])
}
fn ensure_venv(&self) -> Result<(), Error> {
if self.paths.venv.exists() {
print_info_2(&format!(
"Using existing virtualenv: {}",
self.paths.venv.display()
));
} else {
self.create_venv()?;
}
Ok(())
}
fn create_venv(&self) -> Result<(), Error> {
operations::venv::create(&self.paths.venv, &self.python_info, &self.settings)
}
fn expect_venv(&self) -> Result<(), Error> {
operations::venv::expect(&self.paths.venv)
}
pub fn install(&self, post_install_action: PostInstallAction) -> Result<(), Error> {
print_info_1("Preparing project for development");
let lock_path = &self.paths.lock;
if !lock_path.exists() {
return Err(Error::MissingLock {
expected_path: lock_path.to_path_buf(),
});
}
self.ensure_venv()?;
self.install_from_lock()?;
match post_install_action {
PostInstallAction::RunSetupPyDevelop => self.develop()?,
PostInstallAction::None => (),
}
Ok(())
}
fn install_from_lock(&self) -> Result<(), Error> {
let lock_path = &self.paths.lock;
print_info_2(&format!(
"Installing dependencies from {}",
lock_path.display()
));
let lock_name = &self
.paths
.lock
.file_name()
.unwrap_or_else(|| panic!("self.path.lock has no filename component"));
let as_str = lock_name.to_string_lossy();
let cmd = &["python", "-m", "pip", "install", "--requirement", &as_str];
self.venv_runner.run(cmd)
}
pub fn upgrade_pip(&self) -> Result<(), Error> {
print_info_2("Upgrading pip");
let cmd = &["python", "-m", "pip", "install", "pip", "--upgrade"];
self.venv_runner
.run(cmd)
.map_err(|_| Error::PipUpgradeFailed {})
}
pub fn lock(&self, lock_options: &operations::LockOptions) -> Result<(), Error> {
print_info_1("Locking dependencies");
if !self.paths.setup_py.exists() {
return Err(Error::MissingSetupPy {});
}
self.ensure_venv()?;
self.upgrade_pip()?;
self.install_editable()?;
self.lock_dependencies(&lock_options)
}
pub fn bump_in_lock(&self, name: &str, version: &str, git: bool) -> Result<(), Error> {
print_info_1(&format!("Bumping {} to {} ...", name, version));
let metadata = self.get_metadata()?;
operations::bump_in_lock(&self.paths.lock, name, version, git, &metadata)
}
pub fn run_and_die<T: AsRef<str>>(&self, cmd: &[T]) -> Result<(), Error> {
self.expect_venv()?;
self.venv_runner.run_and_die(cmd)
}
pub fn run<T: AsRef<str>>(&self, cmd: &[T]) -> Result<(), Error> {
self.expect_venv()?;
self.venv_runner.run(cmd)
}
pub fn show_deps(&self) -> Result<(), Error> {
self.venv_runner.run(&["python", "-m", "pip", "list"])
}
pub fn show_venv_path(&self) -> Result<(), Error> {
println!("{}", self.paths.venv.display());
Ok(())
}
pub fn show_venv_bin_path(&self) -> Result<(), Error> {
self.expect_venv()?;
let bin_path = self.venv_runner.binaries_path();
println!("{}", bin_path.display());
Ok(())
}
pub fn show_outdated(&self) -> Result<(), Error> {
#[rustfmt::skip]
let cmd = &[
"python", "-m", "pip",
"list", "--outdated",
"--format", "columns",
];
self.venv_runner.run(cmd)
}
fn install_editable(&self) -> Result<(), Error> {
let mut message = "Installing deps from setup.py".to_string();
if self.settings.production {
message.push_str(" using 'prod' extra dependencies");
} else {
message.push_str(" using 'dev' extra dependencies");
}
print_info_2(&message);
let extra = if self.settings.production {
".[prod]"
} else {
".[dev]"
};
let cmd = &["python", "-m", "pip", "install", "--editable", extra];
self.venv_runner.run(cmd)
}
fn lock_dependencies(&self, lock_options: &operations::LockOptions) -> Result<(), Error> {
let metadata = &self.get_metadata()?;
let frozen_deps = self.get_frozen_deps()?;
let lock_path = &self.paths.lock;
operations::lock_dependencies(lock_path, frozen_deps, lock_options, &metadata)
}
fn get_metadata(&self) -> Result<Metadata, Error> {
let dmenv_version = env!("CARGO_PKG_VERSION");
let python_platform = &self.python_info.platform;
let python_version = &self.python_info.version;
Ok(Metadata {
dmenv_version: dmenv_version.to_string(),
python_platform: python_platform.to_string(),
python_version: python_version.to_string(),
})
}
fn get_frozen_deps(&self) -> Result<Vec<FrozenDependency>, Error> {
let freeze_output = self.run_pip_freeze()?;
let deps: Result<Vec<_>, _> = freeze_output
.lines()
.map(FrozenDependency::from_string)
.collect();
let deps = deps?;
let res: Vec<_> = deps
.into_iter()
.filter(|x| x.name != "pkg-resources")
.collect();
Ok(res)
}
fn run_pip_freeze(&self) -> Result<String, Error> {
let lock_path = &self.paths.lock;
print_info_2(&format!("Generating {}", lock_path.display()));
#[rustfmt::skip]
let cmd = &[
"python", "-m", "pip", "freeze",
"--exclude-editable",
"--all",
"--local",
];
self.venv_runner.get_output(cmd)
}
}