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

Add a trampoline variant that just executes python #8637

Merged
merged 8 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,10 @@ jobs:
cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-console.exe trampolines/uv-trampoline-${{ matrix.target-arch }}-console.exe
cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-gui.exe trampolines/uv-trampoline-${{ matrix.target-arch }}-gui.exe
- name: "Test"
working-directory: ${{ github.workspace }}/crates/uv-trampoline
run: cargo test --target ${{ matrix.target-arch }}-pc-windows-msvc --test *
working-directory: ${{ github.workspace }}
run: |
rustup target add ${{ matrix.target-arch }}-pc-windows-msvc
cargo test -p uv-trampoline-builder --target ${{ matrix.target-arch }}-pc-windows-msvc

typos:
runs-on: ubuntu-latest
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 0 additions & 48 deletions crates/uv-install-wheel/src/wheel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,54 +969,6 @@ mod test {
Ok(())
}

#[test]
#[cfg(all(windows, target_arch = "x86"))]
fn test_launchers_are_small() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This moved into uv-trampoline-builder, size limit bumped

// At time of writing, they are 45kb~ bytes.
assert!(
super::LAUNCHER_I686_GUI.len() < 45 * 1024,
"GUI launcher: {}",
super::LAUNCHER_I686_GUI.len()
);
assert!(
super::LAUNCHER_I686_CONSOLE.len() < 45 * 1024,
"CLI launcher: {}",
super::LAUNCHER_I686_CONSOLE.len()
);
}

#[test]
#[cfg(all(windows, target_arch = "x86_64"))]
fn test_launchers_are_small() {
// At time of writing, they are 45kb~ bytes.
assert!(
super::LAUNCHER_X86_64_GUI.len() < 45 * 1024,
"GUI launcher: {}",
super::LAUNCHER_X86_64_GUI.len()
);
assert!(
super::LAUNCHER_X86_64_CONSOLE.len() < 45 * 1024,
"CLI launcher: {}",
super::LAUNCHER_X86_64_CONSOLE.len()
);
}

#[test]
#[cfg(all(windows, target_arch = "aarch64"))]
fn test_launchers_are_small() {
// At time of writing, they are 45kb~ bytes.
assert!(
super::LAUNCHER_AARCH64_GUI.len() < 45 * 1024,
"GUI launcher: {}",
super::LAUNCHER_AARCH64_GUI.len()
);
assert!(
super::LAUNCHER_AARCH64_CONSOLE.len() < 45 * 1024,
"CLI launcher: {}",
super::LAUNCHER_AARCH64_CONSOLE.len()
);
}

#[test]
fn test_script_executable() -> Result<()> {
// Test with adjacent pythonw.exe
Expand Down
5 changes: 5 additions & 0 deletions crates/uv-trampoline-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ thiserror = { workspace = true }
zip = { workspace = true }

[dev-dependencies]
assert_cmd = { version = "2.0.16" }
assert_fs = { version = "1.1.2" }
anyhow = { version = "1.0.89" }
fs-err = { workspace = true }
which = { workspace = true }
230 changes: 228 additions & 2 deletions crates/uv-trampoline-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,8 @@ pub fn windows_script_launcher(
/// Sort of equivalent to a `python` symlink on Unix.
#[allow(unused_variables)]
pub fn windows_python_launcher(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new

launcher_python_script: &str,
is_gui: bool,
python_executable: impl AsRef<Path>,
is_gui: bool,
) -> Result<Vec<u8>, Error> {
// This method should only be called on Windows, but we avoid `#[cfg(windows)]` to retain
// compilation on all platforms.
Expand All @@ -178,3 +177,230 @@ pub fn windows_python_launcher(

Ok(launcher)
}

#[cfg(all(test, windows))]
#[allow(clippy::print_stdout)]
mod test {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests pulled from uv-trampoline::tests::harness and uv-install-wheel

use std::io::Write;
use std::path::Path;
use std::process::Command;

use anyhow::Result;
use assert_cmd::prelude::OutputAssertExt;
use assert_fs::prelude::PathChild;
use fs_err::File;

use which::which;

use super::{windows_python_launcher, windows_script_launcher};

#[test]
#[cfg(all(windows, target_arch = "x86"))]
fn test_launchers_are_small() {
// At time of writing, they are 45kb~ bytes.
assert!(
super::LAUNCHER_I686_GUI.len() < 45 * 1024,
"GUI launcher: {}",
super::LAUNCHER_I686_GUI.len()
);
assert!(
super::LAUNCHER_I686_CONSOLE.len() < 45 * 1024,
"CLI launcher: {}",
super::LAUNCHER_I686_CONSOLE.len()
);
}

#[test]
#[cfg(all(windows, target_arch = "x86_64"))]
fn test_launchers_are_small() {
// At time of writing, they are 45kb~ bytes.
assert!(
super::LAUNCHER_X86_64_GUI.len() < 45 * 1024,
"GUI launcher: {}",
super::LAUNCHER_X86_64_GUI.len()
);
assert!(
super::LAUNCHER_X86_64_CONSOLE.len() < 45 * 1024,
"CLI launcher: {}",
super::LAUNCHER_X86_64_CONSOLE.len()
);
}

#[test]
#[cfg(all(windows, target_arch = "aarch64"))]
fn test_launchers_are_small() {
// At time of writing, they are 45kb~ bytes.
assert!(
super::LAUNCHER_AARCH64_GUI.len() < 45 * 1024,
"GUI launcher: {}",
super::LAUNCHER_AARCH64_GUI.len()
);
assert!(
super::LAUNCHER_AARCH64_CONSOLE.len() < 45 * 1024,
"CLI launcher: {}",
super::LAUNCHER_AARCH64_CONSOLE.len()
);
}

/// Utility script for the test.
fn get_script_launcher(shebang: &str, is_gui: bool) -> String {
if is_gui {
format!(
r##"{shebang}
# -*- coding: utf-8 -*-
import re
import sys

def make_gui() -> None:
from tkinter import Tk, ttk
root = Tk()
root.title("uv Test App")
frm = ttk.Frame(root, padding=10)
frm.grid()
ttk.Label(frm, text="Hello from uv-trampoline-gui.exe").grid(column=0, row=0)
root.mainloop()

if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(make_gui())
"##
)
} else {
format!(
r##"{shebang}
# -*- coding: utf-8 -*-
import re
import sys

def main_console() -> None:
print("Hello from uv-trampoline-console.exe", file=sys.stdout)
print("Hello from uv-trampoline-console.exe", file=sys.stderr)
for arg in sys.argv[1:]:
print(arg, file=sys.stderr)

if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main_console())
"##
)
}
}

/// See [`uv-install-wheel::wheel::format_shebang`].
fn format_shebang(executable: impl AsRef<Path>) -> String {
// Convert the executable to a simplified path.
let executable = executable.as_ref().display().to_string();
format!("#!{executable}")
}

#[test]
fn console_script_launcher() -> Result<()> {
// Create Temp Dirs
let temp_dir = assert_fs::TempDir::new()?;
let console_bin_path = temp_dir.child("launcher.console.exe");

// Locate an arbitrary python installation from PATH
let python_executable_path = which("python")?;

// Generate Launcher Script
let launcher_console_script =
get_script_launcher(&format_shebang(&python_executable_path), false);

// Generate Launcher Payload
let console_launcher =
windows_script_launcher(&launcher_console_script, false, &python_executable_path)?;

// Create Launcher
File::create(console_bin_path.path())?.write_all(console_launcher.as_ref())?;

println!(
"Wrote Console Launcher in {}",
console_bin_path.path().display()
);

let stdout_predicate = "Hello from uv-trampoline-console.exe\r\n";
let stderr_predicate = "Hello from uv-trampoline-console.exe\r\n";

// Test Console Launcher
#[cfg(windows)]
Command::new(console_bin_path.path())
.assert()
.success()
.stdout(stdout_predicate)
.stderr(stderr_predicate);

let args_to_test = vec!["foo", "bar", "foo bar", "foo \"bar\"", "foo 'bar'"];
let stderr_predicate = format!("{}{}\r\n", stderr_predicate, args_to_test.join("\r\n"));

// Test Console Launcher (with args)
Command::new(console_bin_path.path())
.args(args_to_test)
.assert()
.success()
.stdout(stdout_predicate)
.stderr(stderr_predicate);

Ok(())
}

#[test]
fn console_python_launcher() -> Result<()> {
// Create Temp Dirs
let temp_dir = assert_fs::TempDir::new()?;
let console_bin_path = temp_dir.child("launcher.console.exe");

// Locate an arbitrary python installation from PATH
let python_executable_path = which("python")?;

// Generate Launcher Payload
let console_launcher = windows_python_launcher(&python_executable_path, false)?;

// Create Launcher
File::create(console_bin_path.path())?.write_all(console_launcher.as_ref())?;

println!(
"Wrote Python Launcher in {}",
console_bin_path.path().display()
);

// Test Console Launcher
Command::new(console_bin_path.path())
.arg("-c")
.arg("print('Hello from Python Launcher')")
.assert()
.success()
.stdout("Hello from Python Launcher\r\n");

Ok(())
}

#[test]
#[ignore]
fn gui_launcher() -> Result<()> {
// Create Temp Dirs
let temp_dir = assert_fs::TempDir::new()?;
let gui_bin_path = temp_dir.child("launcher.gui.exe");

// Locate an arbitrary pythonw installation from PATH
let pythonw_executable_path = which("pythonw")?;

// Generate Launcher Script
let launcher_gui_script =
get_script_launcher(&format_shebang(&pythonw_executable_path), true);

// Generate Launcher Payload
let gui_launcher =
windows_script_launcher(&launcher_gui_script, true, &pythonw_executable_path)?;

// Create Launcher
File::create(gui_bin_path.path())?.write_all(gui_launcher.as_ref())?;

println!("Wrote GUI Launcher in {}", gui_bin_path.path().display());

// Test GUI Launcher
// NOTICE: This will spawn a GUI and will wait until you close the window.
Command::new(gui_bin_path.path()).assert().success();

Ok(())
}
}
10 changes: 5 additions & 5 deletions crates/uv-trampoline/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/uv-trampoline/src/bounce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ enum TrampolineKind {
impl TrampolineKind {
const fn magic_number(&self) -> &'static [u8; 4] {
match self {
Self::Script => &[b'U', b'V', b'S', b'C'],
Self::Python => &[b'U', b'V', b'P', b'Y'],
Self::Script => b"UVSC",
Self::Python => b"UVPY",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah whoops, you already did this. :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry; this is one of those things that wasn't worth moving in the rebase since it was a mess

}
}

Expand Down
Loading