Skip to content
Merged
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
81 changes: 81 additions & 0 deletions .github/workflows/better-uv-download-logic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Test without uv embedding

on:
push:
branches: [better-uv-download-logic]

env:
CARGO_TERM_COLOR: always

jobs:
test:
name: Run Tests (Linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable

- name: Build runner
run: cargo build -p pycrucible_runner --target x86_64-unknown-linux-gnu

- name: Run tests
run: |
cargo test -p pycrucible --target x86_64-unknown-linux-gnu
cargo test -p shared --target x86_64-unknown-linux-gnu

smoke-test:
name: Smoke Test PyCrucible without uv embedding
needs: test
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# Linux
- runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
bin_ext: ""
simple: linux
# Windows
- runner: windows-latest
target: x86_64-pc-windows-msvc
bin_ext: ".exe"
simple: windows
# macOS
- runner: macos-latest
target: aarch64-apple-darwin
bin_ext: ""
simple: macos

steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable

- name: Build runner
run: cargo build -p pycrucible_runner --release --target ${{ matrix.target }}

- name: Build PyCrucible
run: cargo build -p pycrucible --release --target ${{ matrix.target }}

- name: Clone example project
run: git clone https://github.com/razorblade23/test-project-for-PyCrucible

- name: Extract and copy binary
shell: bash
run: |
ls ./target/${{ matrix.target }}/release
cp ./target/${{ matrix.target }}/release/pycrucible${{ matrix.bin_ext }} test-project-for-PyCrucible/pycrucible${{ matrix.bin_ext }}

- name: Make binary executable (non-Windows)
if: runner.os != 'Windows'
run: chmod +x test-project-for-PyCrucible/pycrucible${{ matrix.bin_ext }}

- name: Run PyCrucible
working-directory: test-project-for-PyCrucible
run: ./pycrucible${{ matrix.bin_ext }} -e . -o cowsay${{ matrix.bin_ext }} --no-uv-embed --debug

- name: Smoke-test generated artifact
working-directory: test-project-for-PyCrucible
run: |
ls
./cowsay${{ matrix.bin_ext }}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# v0.4.x
## v0.4.0
- Downloading only `uv` binary, disregarding the rest of the archive.
- `--uv-version` - Select the version of uv to download. [default: `0.9.21`]
- Added support for .whl embedding
- new CLI options are provided for this mode only
- `--extract-to-temp` - ["wheel" mode only] - sets configuration option with the same name
Expand Down
1 change: 1 addition & 0 deletions pycrucible.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ entrypoint = "src/main.py"
# debug = false
# extract_to_temp = false
# delete_after_run = false
# uv_version = "0.9.21"

# # Optional - uncomment if you need it
# [source]
Expand Down
7 changes: 7 additions & 0 deletions pycrucible/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ pub struct Cli {
#[arg(default_value_os_t = get_output_dir().join(UV_BINARY))]
pub uv_path: PathBuf,

#[arg(
long,
help = "Path to `uv` executable. If not found, it will be downloaded automatically"
)]
#[arg(default_value_t = String::from("0.9.21"))]
pub uv_version: String,

#[arg(
long,
help = "Disable embedding `uv` binary into the output executable. This will require `uv` to be present alongside (or downloaded) the output binary at runtime."
Expand Down
2 changes: 2 additions & 0 deletions pycrucible/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct CLIOptions {
source_dir: PathBuf,
output_path: PathBuf,
uv_path: PathBuf,
uv_version: String,
no_uv_embed: bool,
extract_to_temp: bool,
delete_after_run: bool,
Expand Down Expand Up @@ -88,6 +89,7 @@ fn main() -> io::Result<()> {
source_dir: payload_path.clone(),
output_path: output_path.clone(),
uv_path: cli.uv_path,
uv_version: cli.uv_version,
no_uv_embed: cli.no_uv_embed,
extract_to_temp: cli.extract_to_temp,
delete_after_run: cli.delete_after_run,
Expand Down
49 changes: 24 additions & 25 deletions pycrucible/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

use crate::{config, runner};
use crate::{debug_println, project};
use shared::uv_handler_v2::{download_and_install_uv_v2, find_or_download_uv};
use shared::uv_handler::find_or_download_uv;
use std::fs::File;
use std::fs::{self, OpenOptions};
use std::io::Read;
use std::io::{self, Cursor, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use zip::{ZipWriter, write::FileOptions};
use std::fs::File;
use std::io::Read;
use zip::ZipArchive;
use zip::{ZipWriter, write::FileOptions};

pub fn find_manifest_file(source_dir: &Path) -> Option<PathBuf> {
if source_dir.join("pyproject.toml").exists() {
Expand All @@ -31,12 +31,12 @@ pub fn find_manifest_file(source_dir: &Path) -> Option<PathBuf> {
}

fn embed_uv(
cli_uv_path: PathBuf,
cli_options: &crate::CLIOptions,
zip: &mut ZipWriter<&mut Cursor<Vec<u8>>>,
options: FileOptions<'_, ()>,
) -> io::Result<Option<()>> {
debug_println!("[payload.embed_uv] - Embedding uv binary into payload");
let uv_path = find_or_download_uv(Some(cli_uv_path));
let uv_path = find_or_download_uv(Some(cli_options.uv_path.clone()), &cli_options.uv_version);
match uv_path {
None => {
eprintln!("Could not find or download uv binary. uv will be required at runtime.");
Expand Down Expand Up @@ -73,20 +73,6 @@ fn write_to_zip(
Ok(())
}

#[cfg(unix)]
fn set_permissions_on_unix(file: PathBuf) -> Result<(), io::Error> {
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&file)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&file, perms)?;
debug_println!("[payload.embed_payload] - Set permissions for uv on linux");
Ok(())
}
}



fn read_wheel_name(path: &str) -> Result<String, Box<dyn std::error::Error>> {
let file = File::open(path)?;
let mut zip = ZipArchive::new(file)?;
Expand Down Expand Up @@ -126,12 +112,14 @@ pub fn embed_payload(

// Update project config with CLI options as we do not use any other file to store these in wheel mode
project_config.options.debug = cli_options.debug;
project_config.options.extract_to_temp = cli_options.extract_to_temp;
project_config.options.delete_after_run = cli_options.delete_after_run;
project_config.options.uv_version = cli_options.uv_version.to_string();

// Check to see if we have a wheel or source files and handle accordingly
match source_files {
project::CollectedSources::Wheel(wheel) => {
project_config.options.extract_to_temp = cli_options.extract_to_temp;
project_config.options.delete_after_run = cli_options.delete_after_run;

let wheel_path = &wheel.absolute_path;
let wheel_file_name =
wheel_path
Expand Down Expand Up @@ -178,10 +166,15 @@ pub fn embed_payload(
debug_println!(
"[payload.embed_payload] - Force uv download flag is set, re-downloading uv"
);
download_and_install_uv_v2(&cli_options.uv_path);
let uv_path = if cli_options.uv_path.exists() {
Some(cli_options.uv_path.clone())
} else {
None
};
find_or_download_uv(uv_path, cli_options.uv_version.as_str());
}
debug_println!("[payload.embed_payload] - Looking for uv binary to embed");
if let Some(_path) = embed_uv(cli_options.uv_path, &mut zip, options)? {
if let Some(_path) = embed_uv(&cli_options, &mut zip, options)? {
debug_println!("[payload.embed_payload] - uv binary embedded successfully");
} else {
eprintln!("Could not find or download uv binary. uv will be required at runtime.");
Expand Down Expand Up @@ -358,14 +351,20 @@ mod tests {
source_dir: src_dir.clone(),
output_path: output_path.clone(),
uv_path: uv_path.clone(),
uv_version: "0.9.21".to_string(),
no_uv_embed: false,
extract_to_temp: true,
delete_after_run: false,
force_uv_download: false,
debug: false,
};

let result = embed_payload(&source_files, &manifest_option, &mut project_config, cli_options);
let result = embed_payload(
&source_files,
&manifest_option,
&mut project_config,
cli_options,
);
assert!(result.is_ok(), "embed_payload should succeed");
assert!(output_path.exists());

Expand Down
Loading