diff --git a/Cargo.toml b/Cargo.toml
index f0826b7..2cbed45 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,7 +40,9 @@ passthrough = [
"PYAPP_DISTRIBUTION_EMBED",
"PYAPP_DISTRIBUTION_FORMAT",
"PYAPP_DISTRIBUTION_PATH",
+ "PYAPP_DISTRIBUTION_PIP_AVAILABLE",
"PYAPP_DISTRIBUTION_PYTHON_PATH",
+ "PYAPP_DISTRIBUTION_SITE_PACKAGES_PATH",
"PYAPP_DISTRIBUTION_SOURCE",
"PYAPP_DISTRIBUTION_VARIANT",
"PYAPP_EXEC_CODE",
@@ -64,4 +66,5 @@ passthrough = [
"PYAPP_PYTHON_VERSION",
"PYAPP_SELF_COMMAND",
"PYAPP_SKIP_INSTALL",
+ "PYAPP_UPGRADE_VIRTUALENV",
]
diff --git a/build.rs b/build.rs
index ae74075..785fec5 100644
--- a/build.rs
+++ b/build.rs
@@ -11,6 +11,9 @@ use regex::Regex;
const DEFAULT_PYTHON_VERSION: &str = "3.11";
const KNOWN_DISTRIBUTION_FORMATS: &[&str] = &["tar|bzip2", "tar|gzip", "tar|zstd", "zip"];
+const DEFAULT_CPYTHON_SOURCE: &str =
+ "https://github.com/indygreg/python-build-standalone/releases/download/";
+const DEFAULT_PYPY_SOURCE: &str = "https://downloads.python.org/pypy/";
// Python version in the form MAJOR.MINOR
// Target OS https://doc.rust-lang.org/reference/conditional-compilation.html#target_os
@@ -154,6 +157,41 @@ const DEFAULT_CPYTHON_DISTRIBUTIONS: &[(&str, &str, &str, &str, &str, &str)] = &
("3.7", "macos", "x86_64", "", "", "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-pgo-20200823T2228.tar.zst"),
];
+// See https://downloads.python.org/pypy/
+#[rustfmt::skip]
+const DEFAULT_PYPY_DISTRIBUTIONS: &[(&str, &str, &str, &str, &str)] = &[
+ ("pypy3.10", "linux", "aarch64", "gnu",
+ "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2"),
+ ("pypy3.10", "linux", "x86_64", "gnu",
+ "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2"),
+ ("pypy3.10", "windows", "x86_64", "msvc",
+ "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip"),
+ ("pypy3.10", "macos", "aarch64", "",
+ "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2"),
+ ("pypy3.10", "macos", "x86_64", "",
+ "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2"),
+ ("pypy3.9", "linux", "aarch64", "gnu",
+ "https://downloads.python.org/pypy/pypy3.9-v7.3.12-aarch64.tar.bz2"),
+ ("pypy3.9", "linux", "x86_64", "gnu",
+ "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux64.tar.bz2"),
+ ("pypy3.9", "windows", "x86_64", "msvc",
+ "https://downloads.python.org/pypy/pypy3.9-v7.3.12-win64.zip"),
+ ("pypy3.9", "macos", "aarch64", "",
+ "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_arm64.tar.bz2"),
+ ("pypy3.9", "macos", "x86_64", "",
+ "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_x86_64.tar.bz2"),
+ ("pypy2.7", "linux", "aarch64", "gnu",
+ "https://downloads.python.org/pypy/pypy2.7-v7.3.12-aarch64.tar.bz2"),
+ ("pypy2.7", "linux", "x86_64", "gnu",
+ "https://downloads.python.org/pypy/pypy2.7-v7.3.12-linux64.tar.bz2"),
+ ("pypy2.7", "windows", "x86_64", "msvc",
+ "https://downloads.python.org/pypy/pypy2.7-v7.3.12-win64.zip"),
+ ("pypy2.7", "macos", "aarch64", "",
+ "https://downloads.python.org/pypy/pypy2.7-v7.3.12-macos_arm64.tar.bz2"),
+ ("pypy2.7", "macos", "x86_64", "",
+ "https://downloads.python.org/pypy/pypy2.7-v7.3.12-macos_x86_64.tar.bz2"),
+];
+
fn set_runtime_variable(name: &str, value: impl Display) {
println!("cargo:rustc-env={}={}", name, value)
}
@@ -250,8 +288,7 @@ fn get_distribution_source() -> String {
abi
};
- for (python_version, platform, arch, abi, variant, url) in DEFAULT_CPYTHON_DISTRIBUTIONS.iter()
- {
+ for (python_version, platform, arch, abi, variant, url) in DEFAULT_CPYTHON_DISTRIBUTIONS {
if python_version == &selected_python_version
&& platform == &selected_platform
&& arch == &selected_arch
@@ -262,6 +299,16 @@ fn get_distribution_source() -> String {
}
}
+ for (python_version, platform, arch, abi, url) in DEFAULT_PYPY_DISTRIBUTIONS {
+ if python_version == &selected_python_version
+ && platform == &selected_platform
+ && arch == &selected_arch
+ && abi == &selected_abi
+ {
+ return url.to_string();
+ }
+ }
+
panic!(
"\n\nNo default distribution source found\nPython version: {}\nPlatform: {}\nArchitecture: {}\nABI: {}\nVariant: {}\n\n",
selected_python_version, selected_platform, selected_arch, selected_abi, selected_variant
@@ -431,6 +478,18 @@ fn set_distribution() {
set_distribution_format(&distribution_source);
set_python_path(&distribution_source);
+ set_site_packages_path(&distribution_source);
+ set_distribution_pip_available(&distribution_source);
+
+ let python_isolation_flag = if get_python_version() == "pypy2.7" {
+ // https://docs.python.org/2/using/cmdline.html#cmdoption-e
+ // https://docs.python.org/2/using/cmdline.html#cmdoption-s
+ "-sE"
+ } else {
+ // https://docs.python.org/3/using/cmdline.html#cmdoption-I
+ "-I"
+ };
+ set_runtime_variable("PYAPP__PYTHON_ISOLATION_FLAG", python_isolation_flag);
}
fn set_distribution_format(distribution_source: &String) {
@@ -462,31 +521,41 @@ fn set_python_path(distribution_source: &str) {
let on_windows = env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows";
let python_path = env::var(distribution_variable).unwrap_or_default();
let relative_path = if !python_path.is_empty() {
- &python_path
- } else if distribution_source
- .starts_with("https://github.com/indygreg/python-build-standalone/releases/download/")
- {
+ python_path
+ } else if distribution_source.starts_with(DEFAULT_CPYTHON_SOURCE) {
if get_python_version() == "3.7" {
if on_windows {
- r"python\install\python.exe"
+ r"python\install\python.exe".to_string()
} else {
- "python/install/bin/python3"
+ "python/install/bin/python3".to_string()
}
} else if on_windows {
- r"python\python.exe"
+ r"python\python.exe".to_string()
+ } else {
+ "python/bin/python3".to_string()
+ }
+ } else if distribution_source.starts_with(DEFAULT_PYPY_SOURCE) {
+ let directory = distribution_source
+ .split('/')
+ .last()
+ .unwrap()
+ .trim_end_matches(".tar.bz2")
+ .trim_end_matches(".zip");
+ if on_windows {
+ format!(r"{}\pypy.exe", directory)
} else {
- "python/bin/python3"
+ format!("{}/bin/pypy3", directory)
}
} else if on_windows {
- "python.exe"
+ "python.exe".to_string()
} else {
- "bin/python3"
+ "bin/python3".to_string()
};
- set_runtime_variable(distribution_variable, relative_path);
+ set_runtime_variable(distribution_variable, &relative_path);
let installation_variable = "PYAPP__INSTALLATION_PYTHON_PATH";
if is_enabled("PYAPP_FULL_ISOLATION") {
- set_runtime_variable(installation_variable, relative_path);
+ set_runtime_variable(installation_variable, &relative_path);
} else if on_windows {
set_runtime_variable(installation_variable, r"Scripts\python.exe");
} else {
@@ -494,6 +563,81 @@ fn set_python_path(distribution_source: &str) {
};
}
+fn set_site_packages_path(distribution_source: &str) {
+ let distribution_variable = "PYAPP_DISTRIBUTION_SITE_PACKAGES_PATH";
+ let on_windows = env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows";
+ let python_version = get_python_version();
+ let site_packages_path = env::var(distribution_variable).unwrap_or_default();
+ let relative_path = if !site_packages_path.is_empty() {
+ site_packages_path
+ } else if distribution_source.starts_with(DEFAULT_CPYTHON_SOURCE) {
+ if python_version == "3.7" {
+ if on_windows {
+ r"python\install\Lib\site-packages".to_string()
+ } else {
+ format!("python/install/lib/python{}/site-packages", python_version)
+ }
+ } else if on_windows {
+ r"python\Lib\site-packages".to_string()
+ } else {
+ format!("python/lib/python{}/site-packages", python_version)
+ }
+ } else if distribution_source.starts_with(DEFAULT_PYPY_SOURCE) {
+ let directory = distribution_source
+ .split('/')
+ .last()
+ .unwrap()
+ .trim_end_matches(".tar.bz2")
+ .trim_end_matches(".zip");
+ if python_version == "pypy2.7" {
+ if on_windows {
+ format!(r"{}\site-packages", directory)
+ } else {
+ format!("{}/site-packages", directory)
+ }
+ } else if on_windows {
+ format!(r"{}\Lib\site-packages", directory)
+ } else {
+ format!("{}/lib/{}/site-packages", directory, python_version)
+ }
+ } else if on_windows {
+ r"Lib\site-packages".to_string()
+ } else {
+ format!("lib/python{}/site-packages", python_version)
+ };
+ set_runtime_variable(distribution_variable, &relative_path);
+
+ let installation_variable = "PYAPP__INSTALLATION_SITE_PACKAGES_PATH";
+ if is_enabled("PYAPP_FULL_ISOLATION") {
+ set_runtime_variable(installation_variable, &relative_path);
+ } else if get_python_version() == "pypy2.7" {
+ set_runtime_variable(installation_variable, "site-packages");
+ } else if on_windows {
+ set_runtime_variable(installation_variable, r"Lib\site-packages");
+ } else {
+ set_runtime_variable(
+ installation_variable,
+ format!("lib/python{}/site-packages", python_version),
+ );
+ };
+}
+
+fn set_distribution_pip_available(distribution_source: &str) {
+ let variable = "PYAPP_DISTRIBUTION_PIP_AVAILABLE";
+ if is_enabled(variable)
+ // Enable if a default source is used and known to have pip installed already
+ || (!distribution_source.is_empty()
+ && !distribution_source.starts_with(DEFAULT_PYPY_SOURCE)
+ && env::var("PYAPP_DISTRIBUTION_SOURCE")
+ .unwrap_or_default()
+ .is_empty())
+ {
+ set_runtime_variable(variable, "1");
+ } else {
+ set_runtime_variable(variable, "0");
+ }
+}
+
fn set_execution_mode() {
let module_variable = "PYAPP_EXEC_MODULE";
let module = env::var(module_variable).unwrap_or_default();
@@ -544,6 +688,15 @@ fn set_isolation_mode() {
}
}
+fn set_upgrade_virtualenv() {
+ let variable = "PYAPP_UPGRADE_VIRTUALENV";
+ if is_enabled(variable) || get_python_version() == "pypy2.7" {
+ set_runtime_variable(variable, "1");
+ } else {
+ set_runtime_variable(variable, "0");
+ }
+}
+
fn set_pip_external() {
let variable = "PYAPP_PIP_EXTERNAL";
if is_enabled(variable) {
@@ -655,6 +808,7 @@ fn main() {
set_distribution();
set_execution_mode();
set_isolation_mode();
+ set_upgrade_virtualenv();
set_pip_external();
set_pip_version();
set_pip_extra_args();
diff --git a/docs/changelog.md b/docs/changelog.md
index 9ac2295..d46f30f 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -8,14 +8,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## Unreleased
+***Changed:***
+
+- Custom distributions should now define the relative path to the `site-packages` directory
+
***Added:***
-- Add support for distributions with `bzip2` compression
+- Add support for PyPy distributions
+- Add the `PYAPP_UPGRADE_VIRTUALENV` option to create virtual environments with `virtualenv` rather than the stdlib's `venv`
+- Add support for custom distributions with `bzip2` compression
***Fixed:***
- Properly handle cases where temporary files are on different filesystems
- Fix regression in the `metadata` management command on Windows
+- Improve error messages when running binaries that were misconfigured
## 0.8.0 - 2023-06-09
diff --git a/docs/config.md b/docs/config.md
index a8823ee..6d61869 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -68,6 +68,16 @@ Some distributions have [variants](https://gregoryszorc.com/docs/python-build-st
| Linux |
|
| Windows | |
+#### PyPy
+
+| ID |
+| --- |
+| `pypy2.7` |
+| `pypy3.9` |
+| `pypy3.10` |
+
+The source of distributions is the [PyPy](https://www.pypy.org) project.
+
### Custom
You may explicitly set the `PYAPP_DISTRIBUTION_SOURCE` option which overrides the [known](#known) distribution settings. The source must be a URL that points to an archived version of the desired Python distribution.
@@ -89,6 +99,14 @@ The following formats are supported for the `PYAPP_DISTRIBUTION_FORMAT` option,
You may set the relative path to the Python executable after unpacking the archive with the `PYAPP_DISTRIBUTION_PYTHON_PATH` option. The default is `python.exe` on Windows and `bin/python3` on all other platforms.
+#### Site packages location
+
+You may set the relative path to the [`site-packages`](https://docs.python.org/3/library/site.html) directory after unpacking the archive with the `PYAPP_DISTRIBUTION_SITE_PACKAGES_PATH` option. The default is `Lib\site-packages` on Windows and `lib/python/site-packages` on all other platforms where `` is the [distribution ID](#known) is defined.
+
+#### pip availability
+
+You may indicate whether pip is already installed by setting the `PYAPP_DISTRIBUTION_PIP_AVAILABLE` option to `true` or `1`. This elides the check for installation when [upgraded virtual environments](#virtual-environments) are enabled.
+
### Embedding ### {: #distribution-embedding }
You may set the `PYAPP_DISTRIBUTION_EMBED` option to `true` or `1` to embed the distribution in the executable at build time to avoid fetching it at runtime. When distribution embedding is enabled, you can set the `PYAPP_DISTRIBUTION_PATH` option to use a local path rather than fetching the source.
@@ -104,7 +122,7 @@ You may set the `PYAPP_PIP_EXTERNAL` option to `true` or `1` to use the [standal
By default, the latest version is used. You may use a specific `X.Y.Z` version by setting the `PYAPP_PIP_VERSION` option.
!!! tip
- This provides a significant installation speed up when [full isolation](#isolation) is not enabled.
+ This provides a significant installation speed up when [full isolation](#full-isolation) is not enabled.
### Extra arguments
@@ -114,10 +132,14 @@ You may set the `PYAPP_PIP_EXTRA_ARGS` option to provide extra arguments to the
You may set the `PYAPP_PIP_ALLOW_CONFIG` option to `true` or `1` to allow the use of environment variables and other configuration at runtime.
-## Isolation
+## Full isolation
You may set the `PYAPP_FULL_ISOLATION` option to `true` or `1` to provide each installation with a full copy of the distribution rather than a virtual environment.
+## Virtual environments
+
+When [full isolation](#full-isolation) is not enabled, you may set the `PYAPP_UPGRADE_VIRTUALENV` option to `true` or `1` to create virtual environments with [virtualenv](https://github.com/pypa/virtualenv) rather than the standard library's `venv` module.
+
## Skipping project installation
You may set the `PYAPP_SKIP_INSTALL` option to `true` or `1` to skip installing the project in the distribution. This allows for entirely predefined distributions and thus no network calls at runtime if used in conjunction with [distribution embedding](#distribution-embedding).
diff --git a/docs/runtime.md b/docs/runtime.md
index 747f967..caa7962 100644
--- a/docs/runtime.md
+++ b/docs/runtime.md
@@ -42,7 +42,7 @@ flowchart TD
MNGCMD -- No --> EXECUTE
MNGCMD -- Yes --> MANAGE[[Run management command]]
click DISTEMBEDDED href "../config/#distribution-embedding"
- click FULLISOLATION href "../config/#isolation"
+ click FULLISOLATION href "../config/#full-isolation"
click EXTERNALPIP href "../config/#externally-managed"
click PROJEMBEDDED href "../config/#project-embedding"
click DEPFILE href "../config/#dependency-file"
diff --git a/src/app.rs b/src/app.rs
index 0ea9096..0eef33e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -37,6 +37,10 @@ fn installation_python_path() -> String {
env!("PYAPP__INSTALLATION_PYTHON_PATH").into()
}
+fn installation_site_packages_path() -> String {
+ env!("PYAPP__INSTALLATION_SITE_PACKAGES_PATH").into()
+}
+
pub fn exposed_command() -> String {
env!("PYAPP__EXPOSED_COMMAND").into()
}
@@ -45,6 +49,10 @@ pub fn distribution_id() -> String {
env!("PYAPP__DISTRIBUTION_ID").into()
}
+pub fn python_isolation_flag() -> String {
+ env!("PYAPP__PYTHON_ISOLATION_FLAG").into()
+}
+
pub fn distribution_source() -> String {
env!("PYAPP_DISTRIBUTION_SOURCE").into()
}
@@ -57,6 +65,10 @@ pub fn distribution_python_path() -> String {
env!("PYAPP_DISTRIBUTION_PYTHON_PATH").into()
}
+pub fn distribution_pip_available() -> bool {
+ env!("PYAPP_DISTRIBUTION_PIP_AVAILABLE") == "1"
+}
+
pub fn project_name() -> String {
env!("PYAPP_PROJECT_NAME").into()
}
@@ -105,6 +117,10 @@ pub fn full_isolation() -> bool {
env!("PYAPP_FULL_ISOLATION") == "1"
}
+pub fn upgrade_virtualenv() -> bool {
+ env!("PYAPP_UPGRADE_VIRTUALENV") == "1"
+}
+
pub fn skip_install() -> bool {
env!("PYAPP_SKIP_INSTALL") == "1"
}
@@ -121,6 +137,10 @@ pub fn python_path(installation_directory: &Path) -> PathBuf {
installation_directory.join(installation_python_path())
}
+pub fn site_packages_path(installation_directory: &Path) -> PathBuf {
+ installation_directory.join(installation_site_packages_path())
+}
+
pub fn cache_directory() -> PathBuf {
platform_dirs().cache_dir().to_path_buf()
}
diff --git a/src/commands/self_cmd/metadata.rs b/src/commands/self_cmd/metadata.rs
index d73cae6..06cf4b4 100644
--- a/src/commands/self_cmd/metadata.rs
+++ b/src/commands/self_cmd/metadata.rs
@@ -17,30 +17,7 @@ impl Cli {
return Ok(());
}
- let python = app::python_path(&installation_directory);
- let site_packages = if cfg!(windows) {
- (|| Some(python.parent()?.parent()?.join("Lib").join("site-packages")))()
- } else {
- (|| {
- let lib_dir = python.parent()?.parent()?.join("lib");
- let version_dir =
- fs::read_dir(lib_dir)
- .ok()?
- .filter_map(Result::ok)
- .find(|entry| {
- let file_name = entry.file_name().to_string_lossy().to_string();
- file_name.starts_with("python") || file_name.starts_with("pypy")
- })?;
-
- Some(version_dir.path().join("site-packages"))
- })()
- };
-
- let site_packages = if let Some(site_packages) = site_packages.filter(|p| p.is_dir()) {
- site_packages
- } else {
- return Ok(());
- };
+ let site_packages = app::site_packages_path(&installation_directory);
let expected_prefix = format!("{}-", app::project_name().replace('-', "_"));
let metadata_file = fs::read_dir(site_packages).ok().and_then(|entries| {
diff --git a/src/commands/self_cmd/update.rs b/src/commands/self_cmd/update.rs
index 87e7956..e9cd9f4 100644
--- a/src/commands/self_cmd/update.rs
+++ b/src/commands/self_cmd/update.rs
@@ -48,9 +48,14 @@ impl Cli {
let dependency_file = app::project_dependency_file();
let (status, output) = if dependency_file.is_empty() {
command.arg(app::project_name().as_str());
- distribution::pip_install(command, wait_message)?
+ distribution::pip_install(command, wait_message, &installation_directory)?
} else {
- distribution::pip_install_dependency_file(&dependency_file, command, wait_message)?
+ distribution::pip_install_dependency_file(
+ &dependency_file,
+ command,
+ wait_message,
+ &installation_directory,
+ )?
};
if !status.success() {
diff --git a/src/distribution.rs b/src/distribution.rs
index 199d155..d03acd1 100644
--- a/src/distribution.rs
+++ b/src/distribution.rs
@@ -11,9 +11,7 @@ use crate::{app, compression, fs_utils, network, process};
pub fn python_command(python: &PathBuf) -> Command {
let mut command = Command::new(python);
-
- // https://docs.python.org/3/using/cmdline.html#cmdoption-I
- command.arg("-I");
+ command.arg(app::python_isolation_flag());
command
}
@@ -140,6 +138,10 @@ pub fn materialize(installation_directory: &PathBuf) -> Result<()> {
err
);
})?;
+
+ if !app::skip_install() {
+ ensure_base_pip(installation_directory, installation_directory)?;
+ }
} else {
let unpacked_distribution = distributions_dir.join(format!("_{}", app::distribution_id()));
if !unpacked_distribution.is_dir() {
@@ -160,18 +162,53 @@ pub fn materialize(installation_directory: &PathBuf) -> Result<()> {
let mut command =
python_command(&unpacked_distribution.join(app::distribution_python_path()));
- command.args(["-m", "venv"]);
- if app::pip_external() {
- command.arg("--without-pip");
+
+ if app::upgrade_virtualenv() {
+ ensure_base_pip(&unpacked_distribution, installation_directory)?;
+
+ let mut upgrade_command =
+ python_command(&unpacked_distribution.join(app::distribution_python_path()));
+ upgrade_command.args([
+ "-m",
+ "pip",
+ "install",
+ "--upgrade",
+ "--isolated",
+ "--disable-pip-version-check",
+ "--no-warn-script-location",
+ "virtualenv",
+ ]);
+ let (status, output) = run_setup_command(
+ upgrade_command,
+ "Upgrading virtualenv".to_string(),
+ installation_directory,
+ )?;
+ check_setup_status(status, output, installation_directory)?;
+
+ command.args(["-m", "virtualenv"]);
+ if app::pip_external() {
+ command.arg("--no-pip");
+ }
+ } else {
+ command.args(["-m", "venv"]);
+ if app::pip_external() {
+ command.arg("--without-pip");
+ }
}
+
command.arg(installation_directory.to_string_lossy().as_ref());
- process::wait_for(command, "Creating virtual environment".to_string())?;
+ let (status, output) = run_setup_command(
+ command,
+ "Creating virtual environment".to_string(),
+ installation_directory,
+ )?;
+ check_setup_status(status, output, installation_directory)?;
}
Ok(())
}
-fn install_project(installation_directory: &PathBuf) -> Result<()> {
+fn install_project(installation_directory: &Path) -> Result<()> {
let install_target = format!("{} {}", app::project_name(), app::project_version());
let binary_only = app::pip_extra_args().contains("--only-binary :all:")
|| app::pip_extra_args().contains("--only-binary=:all:");
@@ -199,7 +236,7 @@ fn install_project(installation_directory: &PathBuf) -> Result<()> {
} else {
format!("Installing {}", install_target)
};
- pip_install(command, wait_message)?
+ pip_install(command, wait_message, installation_directory)
} else {
let wait_message = if binary_only {
format!("Unpacking {}", install_target)
@@ -210,30 +247,35 @@ fn install_project(installation_directory: &PathBuf) -> Result<()> {
let dependency_file = app::project_dependency_file();
if dependency_file.is_empty() {
command.arg(format!("{}=={}", app::project_name(), app::project_version()).as_str());
- pip_install(command, wait_message)?
+ pip_install(command, wait_message, installation_directory)
} else {
- pip_install_dependency_file(&dependency_file, command, wait_message)?
+ pip_install_dependency_file(
+ &dependency_file,
+ command,
+ wait_message,
+ installation_directory,
+ )
}
- };
-
- if !status.success() {
- fs::remove_dir_all(installation_directory).ok();
- println!("{}", output.trim_end());
- exit(status.code().unwrap_or(1));
- }
+ }?;
+ check_setup_status(status, output, installation_directory)?;
Ok(())
}
-pub fn pip_install(command: Command, wait_message: String) -> Result<(ExitStatus, String)> {
- ensure_pip()?;
- process::wait_for(command, wait_message)
+pub fn pip_install(
+ command: Command,
+ wait_message: String,
+ installation_directory: &Path,
+) -> Result<(ExitStatus, String)> {
+ ensure_external_pip()?;
+ run_setup_command(command, wait_message, installation_directory)
}
pub fn pip_install_dependency_file(
dependency_file: &String,
mut command: Command,
wait_message: String,
+ installation_directory: &Path,
) -> Result<(ExitStatus, String)> {
let dir = tempdir().with_context(|| "unable to create temporary directory")?;
let file_name = app::project_dependency_file_name();
@@ -250,11 +292,27 @@ pub fn pip_install_dependency_file(
command.args(["-r", temp_path.to_string_lossy().as_ref()]);
- ensure_pip()?;
- process::wait_for(command, wait_message)
+ ensure_external_pip()?;
+ run_setup_command(command, wait_message, installation_directory)
+}
+
+fn ensure_base_pip(distribution_directory: &Path, installation_directory: &Path) -> Result<()> {
+ if app::distribution_pip_available() {
+ return Ok(());
+ }
+
+ let mut command = python_command(&distribution_directory.join(app::distribution_python_path()));
+ command.args(["-m", "ensurepip"]);
+
+ run_setup_command(
+ command,
+ "Validating pip".to_string(),
+ installation_directory,
+ )?;
+ Ok(())
}
-fn ensure_pip() -> Result<()> {
+fn ensure_external_pip() -> Result<()> {
if !app::pip_external() {
return Ok(());
}
@@ -296,3 +354,32 @@ fn ensure_pip() -> Result<()> {
fs_utils::move_temp_file(&temp_path, &external_pip)
}
+
+fn run_setup_command(
+ command: Command,
+ message: String,
+ installation_directory: &Path,
+) -> Result<(ExitStatus, String)> {
+ let (status, output) = process::wait_for(command, message).with_context(|| {
+ format!(
+ "could not run Python, verify distribution build metadata options: {}",
+ app::python_path(installation_directory).display()
+ )
+ })?;
+
+ Ok((status, output))
+}
+
+fn check_setup_status(
+ status: ExitStatus,
+ output: String,
+ installation_directory: &Path,
+) -> Result<()> {
+ if !status.success() {
+ fs::remove_dir_all(installation_directory).ok();
+ println!("{}", output.trim_end());
+ exit(status.code().unwrap_or(1));
+ }
+
+ Ok(())
+}