Skip to content

Comments

Add uvx alias for uv tool run#4632

Merged
zanieb merged 1 commit intomainfrom
zb/uvx
Jul 2, 2024
Merged

Add uvx alias for uv tool run#4632
zanieb merged 1 commit intomainfrom
zb/uvx

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
@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 requested a review from charliermarsh June 28, 2024 20:25
@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?

@zanieb
Copy link
Member Author

zanieb commented Jul 1, 2024

cargo-dist uses various methods depending on the distribution target, not just a symbolic link. I worry about having a different implementation across platforms, the complexity of handling this as a distribution special-case, and the loss of control. Here we have a single consistent implementation that we can invoke via cargo run and tested in our standard suite. We have explicit control over the entry point (rather than inferring that it's been used based on the sys arguments).

I think that we can and probably should explore alternative approaches, but this seems like the lower-risk approach to start with.

@zanieb
Copy link
Member Author

zanieb commented Jul 1, 2024

I can't find an obvious way to strip just one of the binaries.

@zanieb zanieb merged commit ec2723a into main Jul 2, 2024
@zanieb zanieb deleted the zb/uvx branch July 2, 2024 01:42
@zanieb zanieb mentioned this pull request Jul 2, 2024
zanieb added a commit that referenced this pull request Jul 10, 2024
This is pulled out of #4632 — a user noted that it would be useful to
use the `uv` crate from Rust. This makes it way easier to invoke `uv`
from Rust with arbitrary arguments as well as use various functionality
in the `uv` crate.

Note this is no longer needed for #4632 and is not particularly urgent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to the command line interface preview Experimental behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add uvx alias for uv tool run

4 participants