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 uvx alias for uv tool run #4632

Merged
merged 1 commit into from
Jul 2, 2024
Merged

Add uvx alias for uv tool run #4632

merged 1 commit into from
Jul 2, 2024

Conversation

zanieb
Copy link
Member

@zanieb zanieb commented Jun 28, 2024

Closes #4476

Originally, this used the changes in #4642 to invoke main() from a uvx binary. This had the benefit of uvx being entirely standalone at the cost of doubling our artifact size. We think that's the incorrect trade-off.

Instead, we assume uvx is always next to uv and create a tiny binary (<1MB) that invokes uv in a child process. This seems preferable to a cargo-dist alias because we have more control over it. This binary should "just work" for all of our cargo-dist distributions and installers, but we'll need to add a new entry point for our PyPI distribution. I'll probably tackle support there separately?

❯ ls -lah target/release/uv target/release/uvx
-rwxr-xr-x  1 zb  staff    31M Jun 28 23:23 target/release/uv
-rwxr-xr-x  1 zb  staff   452K Jun 28 23:22 target/release/uvx

This includes some small overhead:

❯ hyperfine --shell=none --warmup=100 './target/release/uv tool run --help' './target/release/uvx --help' --min-runs 2000
Benchmark 1: ./target/release/uv tool run --help
  Time (mean ± σ):       2.2 ms ±   0.1 ms    [User: 1.3 ms, System: 0.5 ms]
  Range (min … max):     2.0 ms …   4.0 ms    2000 runs
 
  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
 
Benchmark 2: ./target/release/uvx --help
  Time (mean ± σ):       2.9 ms ±   0.1 ms    [User: 1.7 ms, System: 0.9 ms]
  Range (min … max):     2.8 ms …   4.2 ms    2000 runs
 
  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
 
Summary
  ./target/release/uv tool run --help ran
    1.35 ± 0.09 times faster than ./target/release/uvx --help

I presume there may be some other downsides to a child process? The wrapper is a little awkward. We could consider execv but this is complicated across platforms. An example implementation of that over in monotrail.

@zanieb zanieb added cli Related to the command line interface preview Experimental behavior labels Jun 28, 2024
crates/uv/src/lib.rs Outdated Show resolved Hide resolved
@zanieb
Copy link
Member Author

zanieb commented Jun 28, 2024

It turns out cargo-dist supports aliases! but I don't think that's actually what we want here, we have more control this way.

@zanieb
Copy link
Member Author

zanieb commented Jun 28, 2024

I think we'd follow this with some special-casing to the help menu to display uvx instead of uv tool run when used.

@zanieb zanieb marked this pull request as ready for review June 28, 2024 20:25
@charliermarsh
Copy link
Member

This is great, but one early concern: will this double the size of the wheel, by shipping two separate binaries that are mostly identical?

@zanieb
Copy link
Member Author

zanieb commented Jun 28, 2024

@charliermarsh great question — ideally we'd make this binary teeeenie tiny. I'll investigate that but it sounds like I'll need to duplicate a lot more code?

@zanieb
Copy link
Member Author

zanieb commented Jun 28, 2024

Mm yeah each of these is going to be large...

❯ ls -lah target/debug/uv target/debug/uvx
-rwxr-xr-x  1 zb  staff    80M Jun 28 16:53 target/debug/uv
-rwxr-xr-x  1 zb  staff    80M Jun 28 16:53 target/debug/uvx

Maybe a cargo-dist alias is the way to go.

@zanieb zanieb marked this pull request as draft June 28, 2024 22:34
@sgammon
Copy link

sgammon commented Jun 28, 2024

@zanieb can't uvx assume it is always alongside uv, and dispatch through the main binary? Thus it would just be an alias for a longer uv command, and much smaller as a result?

Edit: Or, potentially, an alias, and simply detect uvx as the first argument in argv

@zanieb
Copy link
Member Author

zanieb commented Jun 29, 2024

Yeah I think we're going to need to do something like that. I think the binary size increase in unacceptable for us and there's no reason for uvx to be standalone today. I think we have options like...

  • A cargo-dist alias
    • We could need to roll our own alias in our PyPI distributions
    • The way the alias works differs across platforms and distribution methods
    • The sys args are not reliable and differ across platforms, can we use them to know uvx was invoked reliably?
  • A bespoke alias
    • We would need to figure out how to distribute this across platforms
  • A new binary in the uv binary which just dispatches to a child process
    • Does this come have acceptable overhead?
    • Can it assume that it is always next to uv or do we need to support some discovery?

@zanieb zanieb changed the base branch from main to zb/lib June 29, 2024 04:16
@zanieb
Copy link
Member Author

zanieb commented Jun 29, 2024

Alright, this is nice and spicy.

❯ ls -lah target/debug/uv target/debug/uvx
-rwxr-xr-x  1 zb  staff    81M Jun 28 23:16 target/debug/uv
-rwxr-xr-x  1 zb  staff   723K Jun 28 23:16 target/debug/uvx
❯ ./target/debug/uvx
Run a tool

Usage: uv tool run [OPTIONS] <COMMAND>

let result = run();
match result {
// Fail with 2 if the status cannot be cast to an exit code
Ok(status) => u8::try_from(status.code().unwrap_or(2)).unwrap_or(2).into(),
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 feels a little dubious, but is probably fine in practice. The documentation suggests the status code is just truncated to a u8 on Unix anyway? Definitely open to alternative approaches here.

Comment on lines +13 to +14
let uv = bin.join("uv");
let args = ["tool", "run"]
Copy link
Member Author

Choose a reason for hiding this comment

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

In the future, this might invoke a hidden uv x so we can special case the help menu and such?

@zanieb zanieb marked this pull request as ready for review June 29, 2024 04:36
@zanieb zanieb changed the base branch from zb/lib to main June 29, 2024 04:40
Copy link
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

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

How did you think about the tradeoffs between this approach and using a symlink?

I'm actually shocked that this is 456 KB, so big! I wonder if we can strip it? https://github.com/johnthagen/min-sized-rust?tab=readme-ov-file#strip-symbols-from-binary

@konstin
Copy link
Member

konstin commented Jul 1, 2024

The sys args are not reliable and differ across platforms, can we use them to know uvx was invoked reliably?

On unix definitely, a lot of binaries rely on moonlighting as different binaries, and i found it to also work on windows. Given that cargo-dist supports them, can we build on their experience?