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

feat(venv): add relocatable flag #5515

Merged
merged 6 commits into from
Jul 29, 2024
Merged

Conversation

paveldikov
Copy link
Contributor

@paveldikov paveldikov commented Jul 28, 2024

Summary

Adds a --relocatable CLI arg to uv venv. This flag does two things:

  • ensures that the associated activation scripts do not rely on a hardcoded
    absolute path to the virtual environment (to the extent possible; .csh and
    .nu left as-is)
  • persists a relocatable flag in pyvenv.cfg.

The flag in pyvenv.cfg in turn instructs the wheel Installer to create script
entrypoints in a relocatable way (use exec trick + dirname $0 on POSIX;
use relative path to python[w].exe on Windows).

Fixes: #3863

Test Plan

  • Relocatable console scripts covered as additional scenarios in existing test cases.
  • Integration testing of boilerplate generation in venv.
  • Manual testing of uv venv with and without --relocatable

/// Other scripts may be adjusted if they ship with a generic `#!python[w]` shebang,
/// and binaries are left as-is.
#[arg(long)]
pub relocatable: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Likely worth marking this as a preview/experimental flag

Copy link
Contributor Author

Choose a reason for hiding this comment

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

would that be a simple hide = true, or is there a special annotation for that? (couldn't find one)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was thinking using PreviewMode for this in venv_impl

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added warning (assuming that's what you meant?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, thanks!

@charliermarsh
Copy link
Member

This has a little bit of overlap with #5509 which just does the installer piece and isn't exposed to users.

@charliermarsh
Copy link
Member

One potential issue here is that users can't use relocatable with --target or --prefix, since those commands don't operate within a virtualenv 🤔

@paveldikov
Copy link
Contributor Author

This has a little bit of overlap with #5509 which just does the installer piece and isn't exposed to users.

Yes, I see that! Feel free to cherry-pick pieces out of this as you see fit (and vice versa -- let me know if I am straying too far away from the direction you had in mind)

@paveldikov
Copy link
Contributor Author

One potential issue here is that users can't use relocatable with --target or --prefix, since those commands don't operate within a virtualenv 🤔

I thought --python was vastly more recommended for this very reason?

@paveldikov paveldikov marked this pull request as ready for review July 28, 2024 17:27
@@ -131,6 +131,7 @@ fn copy_and_hash(reader: &mut impl Read, writer: &mut impl Write) -> io::Result<
fn format_shebang(executable: impl AsRef<Path>, os_name: &str) -> String {
// Convert the executable to a simplified path.
let executable = executable.as_ref().simplified_display().to_string();
let is_relocatable = !executable.contains(['\\', '/']);
Copy link
Member

Choose a reason for hiding this comment

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

Does this assume that the relative path doesn't contain multiple segments? (That's true for virtual environments, but could be false in general, right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On second thought, this check is probably unnecessary and I might just pass the already-known is_relocatable as an argument, rather than trying to reverse-engineer it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored.

@charliermarsh
Copy link
Member

Yes, I see that! Feel free to cherry-pick pieces out of this as you see fit (and vice versa -- let me know if I am straying too far away from the direction you had in mind)

Sounds good! Ultimately pretty similar, I'll likely merge yours first.

@charliermarsh
Copy link
Member

We might want to add some tests by wiring this up through TestContext (perhaps with a new constructor to make it easiest).

@paveldikov
Copy link
Contributor Author

We might want to add some tests by wiring this up through TestContext (perhaps with a new constructor to make it easiest).

I'll try to dig a little deeper tonight, but if you have examples of similar tests handy, that would be grand!

@charliermarsh
Copy link
Member

@paveldikov -- A basic test would be like install_many in pip_sync.rs. I think we just want light coverage for, like: installing and importing a package; installing and running a script; moving the virtualenv (even just renaming) and doing the same.

@charliermarsh
Copy link
Member

You can run cargo test -p uv --test pip_sync install_many to run an individual test.

Adds a `--relocatable` CLI arg to `uv venv`. This flag does two things:

* ensures that the associated activation scripts do not rely on a hardcoded
  absolute path to the virtual environment (to the extent possible; `.csh` and
  `.nu` left as-is)
* persists a `relocatable` flag in `pyvenv.cfg`.

The flag in `pyvenv.cfg` in turn instructs the wheel `Installer` to create
script entrypoints in a relocatable way (use `exec` trick + `dirname $0` on
POSIX; use relative path to `python[w].exe` on Windows).

Fixes: astral-sh#3863
@paveldikov
Copy link
Contributor Author

paveldikov commented Jul 28, 2024

Added a test case for the boilerplate on the venv side. Let me know if there are other areas in the critical path that you'd like to see covered -- I've mostly focussed on the two extremities (venv and installer) since this is where most of the action is happening.

edit: woops, saw that you'd asked for

  • covering package install scenario
  • check if renaming venv base dir makes entrypoint still work

Going to add those in. Does any of the already-existing stub packages in scripts/ contain meaningful console_scripts?

@paveldikov
Copy link
Contributor Author

Added coverage for pip_install.

installing and importing a package; installing and running a script; moving the virtualenv (even just renaming) and doing the same.

Unless I'm reading this wrong, running a console script also implies importing the package, so I didn't handle that separately.

@charliermarsh
Copy link
Member

Thank you! Haven't reviewed yet but per your last question, I believe black_editable has an entrypoint at least.

@paveldikov
Copy link
Contributor Author

I believe black_editable has an entrypoint at least.

Yep, I did find it and used it specifically for its Hello World :-P

@charliermarsh
Copy link
Member

Cool, this generally looks good to me! I'm going to make a few small tweaks to make it a bit easier to rebase #5509, but not anticipating any major changes. Thank you for the tests too.

// (note: the Windows trampoline binaries natively support relative paths to executable)
if shebang_length > 127 || executable.contains(' ') || is_relocatable {
let prefix = if is_relocatable {
r#""$(CDPATH= cd -- "$(dirname -- "$0")" && echo "$PWD")"/"#
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like there's a check if the directory exists (avoiding readlink/realpath friends) before substituting it with PWD which makes this a bit more cross-posix-platform and handle a couple error scenarios. Interestingly enough, I'd expect the / to be inside the echo though if this was the intention? Unless it's expected if cd fails that the output is "/" instead of "". Thoughts?

Copy link
Contributor Author

@paveldikov paveldikov Jul 29, 2024

Choose a reason for hiding this comment

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

Seems like there's a check if the directory exists (avoiding readlink/realpath friends) before substituting it with PWD which makes this a bit more cross-posix-platform and handle a couple error scenarios.

Yes, that's exactly it, readlink -f and realpath are a real POSIX-feature-availability-matrix nightmare, so that's what the CDPATH= cd -- SOME_PATH && echo "$PWD" part is -- a cross-platform way of doing realpath SOME_PATH.

The -- bits in the middle are there to ensure that $0 is parsed as a positional argument (and never as an option), just in case decided to call their console script -myscript ;^)

Interestingly enough, I'd expect the / to be inside the echo though if this was the intention? Unless it's expected if cd fails that the output is "/" instead of "". Thoughts?

There was no intention behind the slash's positioning, to be honest -- if the echo fails we are in undefined territory -- I think I really just placed it according to my aesthetic whims! Perhaps a set -eo pipefail could further harden this boilerplate to ensure that it fails fast? (need to check if this is portable, though)

I guess one might argue that resolving the path down to absolute is not needed in the first place! I guess that would strip much of the complexity off, leaving you with r#""$(dirname -- "$0")/""#.

@charliermarsh charliermarsh added the enhancement New feature or improvement to existing functionality label Jul 29, 2024
@charliermarsh charliermarsh enabled auto-merge (squash) July 29, 2024 00:01
@@ -37,6 +37,7 @@ pub struct Locks(Mutex<FxHashMap<PathBuf, Arc<Mutex<()>>>>);
#[instrument(skip_all, fields(wheel = %filename))]
pub fn install_wheel(
layout: &Layout,
relocatable: bool,
Copy link
Member

Choose a reason for hiding this comment

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

I moved these next to each other because part of me wishes that it were on Layout (though I don't think creating layout should require querying the filesystem, so leaving them separate).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did originally put this inside of Layout, but for some reason decided to back out of that.

(though I don't think creating layout should require querying the filesystem, so leaving them separate).

Could use the layout.as_relocatable() pattern? (only mark it as relocatable if specifically requested by the caller)

@charliermarsh charliermarsh merged commit cb47aed into astral-sh:main Jul 29, 2024
55 checks passed
@charliermarsh
Copy link
Member

Thanks!

@@ -19,7 +19,7 @@
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@set "VIRTUAL_ENV={{ VIRTUAL_ENV_DIR }}"
@for %%i in ("{{ VIRTUAL_ENV_DIR }}") do @set "VIRTUAL_ENV=%%~fi"
Copy link
Collaborator

@samypr100 samypr100 Jul 29, 2024

Choose a reason for hiding this comment

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

This seems to be for expanding a relative path in command prompt since the previous command didn't, awesome!

@chrisrodrigue
Copy link

chrisrodrigue commented Jul 30, 2024

Please excuse my ignorance… does this mean that portable virtual environments are possible now?

@paveldikov
Copy link
Contributor Author

paveldikov commented Jul 30, 2024

Please excuse my ignorance… does this mean that portable virtual environments are possible now?

It depends -- a lot of people, myself included, would be wary of using the word 'portable'. (it could imply factors such as host OS or system architecture, in which case: no, these environments are defintely not portable.) But on a high level, yes, assuming a homogenised host environment, these virtual environments can be shipped around from one host to another with most (if not all) functionality being retained.

(also, this is a preview feature, so I imagine it is probably premature to talk of it as a done deal. Matter of fact, I just found a bug with the activate script on ksh, which I am now to fix!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or improvement to existing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: decouple "build" and "target" paths in install_wheel API
4 participants