Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom sources for distributions and UV #152

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ passthrough = [
"PYAPP_DISTRIBUTION_PYTHON_PATH",
"PYAPP_DISTRIBUTION_SITE_PACKAGES_PATH",
"PYAPP_DISTRIBUTION_SOURCE",
"PYAPP_DISTRIBUTION_SOURCE_{}",
"PYAPP_DISTRIBUTION_VARIANT",
"PYAPP_EXEC_CODE",
"PYAPP_EXEC_MODULE",
Expand Down Expand Up @@ -82,5 +83,6 @@ passthrough = [
"PYAPP_UPGRADE_VIRTUALENV",
"PYAPP_UV_ENABLED",
"PYAPP_UV_ONLY_BOOTSTRAP",
"PYAPP_UV_SOURCE",
"PYAPP_UV_VERSION",
]
99 changes: 79 additions & 20 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,19 @@ fn check_environment_variable(name: &str) -> String {
value
}

fn filename_from_url(url: &str) -> String {
let parsed =
reqwest::Url::parse(url).unwrap_or_else(|_| panic!("unable to parse URL: {}", &url));

if let Some(segments) = parsed.path_segments() {
if let Some(segment) = segments.last() {
return segment.into();
}
}

panic!("unable to determine artifact name from URL: {}", &url);
}

fn is_enabled(name: &str) -> bool {
["true", "1"].contains(&env::var(name).unwrap_or_default().as_str())
}
Expand Down Expand Up @@ -310,14 +323,36 @@ fn get_python_version() -> String {
DEFAULT_PYTHON_VERSION.to_string()
}

fn get_custom_source(name: &str) -> Option<String> {
let name = name.to_uppercase().replace(".", "_");
let variable_name = format!("PYAPP_DISTRIBUTION_SOURCE_{}", name);
if let Ok(value) = env::var(variable_name) {
if !value.is_empty() {
return Some(value);
}
}
None
}

fn get_distribution_source() -> String {
let selected_python_version = get_python_version();

// Return custom source if specified for this version
if let Some(custom_source) = get_custom_source(&selected_python_version) {
dbg!(
"Using custom source for version {}: {}",
&selected_python_version,
&custom_source
);
return custom_source;
}

// Otherwise, check if there is a global custom source
let distribution_source = env::var("PYAPP_DISTRIBUTION_SOURCE").unwrap_or_default();
if !distribution_source.is_empty() {
return distribution_source;
};

let selected_python_version = get_python_version();

// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
let selected_platform = match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
"windows" => "windows",
Expand Down Expand Up @@ -922,27 +957,51 @@ fn set_uv_only_bootstrap() {
}
}

fn set_uv_version() {
let variable = "PYAPP_UV_VERSION";
let version = env::var(variable).unwrap_or("any".to_string());
set_runtime_variable(variable, version);
fn set_uv_source() {
let source_variable = "PYAPP_UV_SOURCE";
let mut source = env::var(source_variable).unwrap_or_default();

let artifact_name = if !is_enabled("PYAPP_UV_ENABLED") {
"".to_string()
} else if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
// Force MinGW-w64 to use msvc
if env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default() == "gnu" {
if !source.is_empty() {
set_runtime_variable("PYAPP__UV_ARTIFACT_NAME", filename_from_url(&source));

let mut hasher = PortableHash::default();
source.hash(&mut hasher);
set_runtime_variable("PYAPP__UV_VERSION", hasher.finish());
} else {
let version_variable = "PYAPP_UV_VERSION";
let version = env::var(version_variable).unwrap_or("any".to_string());

let artifact_name = if !is_enabled("PYAPP_UV_ENABLED") {
"".to_string()
} else if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
// Force MinGW-w64 to use msvc
if env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default() == "gnu" {
format!(
"uv-{}-pc-windows-msvc.zip",
env::var("CARGO_CFG_TARGET_ARCH").unwrap()
)
} else {
format!("uv-{}.zip", env::var("TARGET").unwrap())
}
} else {
format!("uv-{}.tar.gz", env::var("TARGET").unwrap())
};

source = if version == "any" {
format!(
"uv-{}-pc-windows-msvc.zip",
env::var("CARGO_CFG_TARGET_ARCH").unwrap()
"https://github.com/astral-sh/uv/releases/latest/download/{}",
&artifact_name,
)
} else {
format!("uv-{}.zip", env::var("TARGET").unwrap())
}
} else {
format!("uv-{}.tar.gz", env::var("TARGET").unwrap())
};
set_runtime_variable("PYAPP__UV_ARTIFACT_NAME", artifact_name);
format!(
"https://github.com/astral-sh/uv/releases/download/{}/{}",
&version, &artifact_name,
)
};
set_runtime_variable("PYAPP__UV_ARTIFACT_NAME", artifact_name);
set_runtime_variable("PYAPP__UV_VERSION", &version);
}
set_runtime_variable(source_variable, &source);
}

fn set_skip_install() {
Expand Down Expand Up @@ -1070,7 +1129,7 @@ fn main() {
set_pip_allow_config();
set_uv_enabled();
set_uv_only_bootstrap();
set_uv_version();
set_uv_source();
set_allow_updates();
set_indicator();
set_self_command();
Expand Down
6 changes: 6 additions & 0 deletions docs/config/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ You may explicitly set the `PYAPP_DISTRIBUTION_SOURCE` option which overrides th

Setting this manually may require you to define extra metadata about the distribution that is required for correct [runtime behavior](../runtime.md).

### Version

For greater granularity, you may set the `PYAPP_DISTRIBUTION_SOURCE_<NAME>` option. The source is also the URL to the distribution's archive.

The placeholder `<NAME>` is the uppercased version of the distribution name with periods replaced by underscores e.g. `pypy3.10` would become `PYPY3_10`.

### Format

The following formats are supported for the `PYAPP_DISTRIBUTION_FORMAT` option, with the default chosen based on the ending of the source URL:
Expand Down
6 changes: 6 additions & 0 deletions docs/config/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ You may use a specific `X.Y.Z` version by setting the `PYAPP_UV_VERSION` option.

By default, a version of UV that has already been downloaded by a PyApp application is used. If UV has not yet been downloaded then the latest version is used.

### Source

You may explicitly set the `PYAPP_UV_SOURCE` option in order to download your own UV release archive.

The value must end with the archive's real file extension, which is used to determine the extraction method.

### Only bootstrap

You may set the `PYAPP_UV_ONLY_BOOTSTRAP` option to `true` or `1` to only use UV for virtual environment creation and continue using pip for project installation.
Expand Down
4 changes: 4 additions & 0 deletions docs/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ The following is not intended to be a complete enumeration. Be sure to view the
| [Litestar](https://github.com/litestar-org/litestar-fullstack/blob/dc72eee78173790c3e91b0c095ac9e70ba91bedd/scripts/post-builds.py)
| [Preservation Workbench](https://github.com/Preservation-Workbench/PWCode/blob/e7777806be35bd60ca8c33e677ffd77e38b277d0/build/make.sh)
| [tidal-wave](https://github.com/ebb-earl-co/tidal-wave/blob/6358ede21adb715a053b1e6cc73968933c3bed05/BUILDME.md#pyapp-created-binaries)

## Industry

- [Amadeus](https://amadeus.com) <sup>\[[1](https://github.com/ofek/pyapp/pull/147)|[2](https://github.com/AmadeusITGroup/pyapp)\]</sup>
6 changes: 5 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,11 @@ pub fn uv_only_bootstrap() -> bool {
}

pub fn uv_version() -> String {
env!("PYAPP_UV_VERSION").into()
env!("PYAPP__UV_VERSION").into()
}

pub fn uv_source() -> String {
env!("PYAPP_UV_SOURCE").into()
}

pub fn uv_artifact_name() -> String {
Expand Down
16 changes: 2 additions & 14 deletions src/distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,25 +488,13 @@ fn ensure_uv_available() -> Result<()> {
.with_context(|| format!("unable to create UV cache {}", &managed_uv_cache.display()))?;

let dir = tempdir().with_context(|| "unable to create temporary directory")?;
let artifact_name = app::uv_artifact_name();
let artifact_name: String = app::uv_artifact_name();
let temp_path = dir.path().join(&artifact_name);

let mut f = fs::File::create(&temp_path)
.with_context(|| format!("unable to create temporary file: {}", &temp_path.display()))?;

let url = if uv_version == "any" {
format!(
"https://github.com/astral-sh/uv/releases/latest/download/{}",
&artifact_name,
)
} else {
format!(
"https://github.com/astral-sh/uv/releases/download/{}/{}",
&uv_version, &artifact_name,
)
};

network::download(&url, &mut f, "UV")?;
network::download(&app::uv_source(), &mut f, "UV")?;

if artifact_name.ends_with(".zip") {
compression::unpack_zip(temp_path, dir.path(), "Unpacking UV".to_string())
Expand Down