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 uv crate to be used as a library #4642

Merged
merged 1 commit into from
Jul 10, 2024
Merged

Allow uv crate to be used as a library #4642

merged 1 commit into from
Jul 10, 2024

Conversation

zanieb
Copy link
Member

@zanieb zanieb commented Jun 29, 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.

@zanieb zanieb added the rustlib Related to our Rust library API label Jun 29, 2024
Comment on lines +851 to +987
// `std::env::args` is not `Send` so we parse before passing to our runtime
// https://github.com/rust-lang/rust/pull/48005
let cli = match Cli::try_parse_from(args) {
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 without change from run above because it's not safe to send std::env values across threads depending on the platform.

@zanieb zanieb marked this pull request as ready for review June 29, 2024 04:31
zanieb added a commit that referenced this pull request Jul 2, 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](https://github.com/konstin/poc-monotrail/blob/433af5aed90921e2c3f4b426b3ffffd83f04d3ff/crates/monotrail/src/monotrail.rs#L764-L799).
Copy link
Member

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

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

I am a little unsure of this personally. Maybe it's fine, but I think it would warrant a docstring somewhere saying that this is not officially supported and to Use At Your Own Risk.

My main specific worry is that this will enable calling the entire uv application more than once in the same process. I can't think of anything off the top of my head that will definitely break as a result of this, but if we're leaving state around that gets reused, it could be an issue.

My more vague worry is that we didn't really design uv to be invoked as a library. So there may be other things that can go wrong here.

@sgammon
Copy link

sgammon commented Jul 8, 2024

Hey uv team,

We wanted to share our use case for uv as a library; we are building a polyglot runtime, Elide, which runs Python as part of its supported language engines.

We want to embed a toolchain to obtain dependencies and manage .venv. uv is perfect for this and architected quite well, so it pops out at us as a good option.

Our dispatch style is a bit weird: we are calling uv over a JNI border from the JVM. Other than this, our effective usage of uv should end up being pretty close to what the binary experiences standalone: (1) we intend to only run one uv execution per process, with uv taking over when the user invokes it; (2) we are calling uv over the JNI border with an array of string args, alleviating any need for type sharing or serialization.

We are happy to continue using uv this way even if there is no official API surface yet. It would be cool eventually to surface structured args, but we're happy with strings as long as we must use them.

It is possible, of course, to ship uv as a standalone binary, and invoke it regularly as a subprocess. I considered this route but there are drawbacks:

  • We are in Rust too, and could leverage all the optimization and protection of dispatching over Rust
  • We end up embedding many of the same crate dependencies anyway, so this would actually increase duplication

All in all if you guys strongly feel like uv should be dispatched over a process border instead, we are happy to consider it.

Of course thank you for all your hard work on uv and everything else Astral 😄

Copy link
Member

@konstin konstin left a comment

Choose a reason for hiding this comment

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

I like this because it's helpful for some testing improvements i want to try. There are some differences between using uv through the main function from rust and calling it as a subprocess (we have some global and thread local state), so i'd keep our officially supported and stable interface to calling binary with cli arguments.

@zanieb
Copy link
Member Author

zanieb commented Jul 10, 2024

I added a warning

@zanieb zanieb enabled auto-merge (squash) July 10, 2024 17:08
@zanieb zanieb merged commit c14de2a into main Jul 10, 2024
49 checks passed
@zanieb zanieb deleted the zb/lib branch July 10, 2024 17:15
@dsully
Copy link

dsully commented Jul 11, 2024

I have a use for this as well. Are you planning on publishing uv to crates.io for consumption?

There is an existing 'uv' "universal getter" that has the name currently, but looks abandoned.

@zanieb
Copy link
Member Author

zanieb commented Jul 11, 2024

We aren't prioritizing it — we're far from a stable Rust API and we move very quickly which sometimes requires a git patch for a dependency. We are interested in doing so eventually though.

@zanieb
Copy link
Member Author

zanieb commented Jul 11, 2024

What's your use case? :)

@dsully
Copy link

dsully commented Jul 11, 2024

We have an internal tool that sets up Python environments for developers: The venv, tox, dependencies, etc.

I also have need for calling uv tool install ... for certain tools that are venv-independent.

Unfortunately I can't use git+https://... for dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rustlib Related to our Rust library API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants