Build workspace member graph to determine if members are dependencies#14683
Build workspace member graph to determine if members are dependencies#14683jtfmumm wants to merge 16 commits intorelease/080from
Conversation
Closes #13057 Sets `UV_TOOL_BIN_DIR` to `/usr/local/bin` for all derived images to allow `uv tool install` to work out of the box. Note, when the default image user is overwritten (e.g. `USER <UID>`) by a less privileged one, an alternative writable location would now need to be set by downstream consumers to prevent issues, hence I'm labeling this as a breaking change for 0.8.x release. Relates to astral-sh/uv-docker-example#55 Each image was tested to work with uv tool with `UV_TOOL_BIN_DIR` set to `/usr/local/bin` with the default root user and alternative non-root users to confirm breaking nature of the change.
While reviewing #14107, @oconnor663 pointed out a bug where we allow `uv python pin --rm` to delete the global pin without the `--global` flag. I think that shouldn't be allowed? I'm not 100% certain though.
Right now, `--python-platform linux` to defaults to `manylinux_2_17`. Defaulting to `manylinux_2_17` causes some problems for users, since it means we can't use (e.g.) `manylinux_2_28` wheels, and end up having to build from source. cibuildwheel made `manylinux_2_28` their default in pypa/cibuildwheel#1988, and there's a lot of discussion in pypa/cibuildwheel#1772 and pypa/cibuildwheel#2047. In short, the `manylinux_2014` image is EOL, and the vast majority of consumers now run at least glibc 2.28 (https://mayeut.github.io/manylinux-timeline/):  Note that this only changes the _default_. Users can still compile against `manylinux_2_17` by specifying it.
If `--workspace` is provided, we add all paths as workspace members. If `--no-workspace` is provided, we add all paths as direct path dependencies. If neither is provided, then we add any paths that are under the workspace root as workspace members, and the rest as direct path dependencies. Closes #14524.
If a user specifies `-e /path/to/dir` and `/path/to/dir` in a `uv pip install` command, we want the editable to "win" (rather than erroring due to conflicting URLs). Unfortunately, this behavior meant that when you requested a package as editable and non-editable in conflicting groups, the editable version was _always_ used. This PR modifies the requisite types to use `Option<bool>` rather than `bool` for the `editable` field, so we can determine whether a requirement was explicitly requested as editable, explicitly requested as non-editable, or not specified (as in the case of `/path/to/dir` in a `requirements.txt` file). In the latter case, we allow editables to override the "unspecified" requirement. If a project includes a path dependency twice, once with `editable = true` and once without any `editable` annotation, those are now considered conflicting URLs, and lead to an error, so I've marked this change as breaking. Closes #14139.
We weren't following our usual "destructure all the options" pattern in this function, and several "this isn't actually read from uv.toml" fields slipped through the cracks over time since folks forgot it existed. Fixes part of #14308, although we could still try to make the warning in FilesystemOptions more accurate? You could argue this is a breaking change, but I think it ultimately isn't really, because we were already silently ignoring these fields. Now we properly error.
In the case of `uv sync` all we really need to do is handle the `OutdatedEnvironment` error (precisely the error we yield only on dry-runs when everything Works but we determine things are outdated) in `OperationDiagnostic::report` (the post-processor on all `operations::install` calls) because any diagnostic handled by that gets downgraded to from status 2 to status 1 (although I don't know if that's really intentional or a random other bug in our status handling... but I figured it's best to highlight that other potential status code incongruence than not rely on it 😄). Fixes #12603 --------- Co-authored-by: John Mumm <jtfmumm@gmail.com>
Fixes #14157 --------- Co-authored-by: John Mumm <jtfmumm@gmail.com>
…`) (#14661) Closes #14298 Switch the default build backend for `uv init` from `hatchling` to `uv_build`. This change affects the following two commands: * `uv init --lib` * `uv init [--app] --package` It does not affect `uv init` or `uv init --app` without `--package`. `uv init --build-backend <...>` also works as before. **Before** ``` $ uv init --lib project $ cat project/pyproject.toml [project] name = "project" version = "0.1.0" description = "Add your description here" readme = "README.md" authors = [ { name = "konstin", email = "konstin@mailbox.org" } ] requires-python = ">=3.13.2" dependencies = [] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" ``` **After** ``` $ uv init --lib project $ cat project/pyproject.toml [project] name = "project" version = "0.1.0" description = "Add your description here" readme = "README.md" authors = [ { name = "konstin", email = "konstin@mailbox.org" } ] requires-python = ">=3.13.2" dependencies = [] [build-system] requires = ["uv_build>=0.7.20,<0.8"] build-backend = "uv_build" ``` I cleaned up some tests for consistency in the second commit.
Following #14614 this is non-fatal and has an opt-out so it should be safe to stabilize.
By default, `uv venv <venv-name>` currently removes the `<venv-name`> directory if it exists. This can be surprising behavior: not everyone expects an existing environment to be overwritten. This PR updates the default to fail if a non-empty `<venv-name>` directory already exists and neither `--allow-existing` nor the new `-c/--clear` option is provided (if a TTY is detected, it prompts first). If it's not a TTY, then uv will only warn and not fail for now — we'll make this an error in the future. I've also added a corresponding `UV_VENV_CLEAR` env var. I've chosen to use `--clear` instead of `--force` for this option because it is used by the `venv` module and `virtualenv` and will be familiar to users. I also think its meaning is clearer in this context than `--force` (which could plausibly mean force overwrite just the virtual environment files, which is what our current `--allow-existing` option does). Closes #1472. --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
Closes #5144 e.g. ``` ❯ cargo run -q -- sync --python-preference only-system Using CPython 3.12.6 interpreter at: /opt/homebrew/opt/python@3.12/bin/python3.12 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 9ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ❯ cargo run -q -- sync --python-preference only-managed Using CPython 3.12.1 Removed virtual environment at: .venv Creating virtual environment at: .venv Resolved 9 packages in 14ms Installed 8 packages in 11ms + anyio==4.6.0 + certifi==2024.8.30 + h11==0.14.0 + httpcore==1.0.5 + httpx==0.27.2 + idna==3.10 + ruff==0.6.7 + sniffio==1.3.1 ```
We currently treat path sources as virtual if they do not specify a build system, which is surprising behavior. This PR updates the behavior to treat path sources as packages unless the path source is explicitly marked as `package = false` or its own `tool.uv.package` is set to `false`. Closes #12015 --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
| } | ||
|
|
||
| for (package_name, workspace_member_project) in &member_projects { | ||
| if let Some(dependencies) = &workspace_member_project.project.dependencies { |
There was a problem hiding this comment.
You need to handle optional dependencies and dependency groups, right?
| }, | ||
| ) | ||
| }) | ||
| .collect::<BTreeMap<PackageName, WorkspaceMember>>() |
There was a problem hiding this comment.
Isn't this type annotation redundant given the return signature?
| fn members_from_member_projects( | ||
| member_projects: BTreeMap<PackageName, WorkspaceMemberProject>, | ||
| ) -> BTreeMap<PackageName, WorkspaceMember> { | ||
| let mut graph = DiGraph::new(); |
There was a problem hiding this comment.
What is the graph getting us here? Why can't we just insert directly into a BTreeMap?
| if let Some(dependencies) = &workspace_member_project.project.dependencies { | ||
| let source_node = node_indices[package_name]; | ||
|
|
||
| for dependency in dependencies { |
There was a problem hiding this comment.
I think we're going to want to omit self dependencies, e.g., for add_self
| #[cfg_attr(test, derive(serde::Serialize))] | ||
| pub struct WorkspaceMember { | ||
| /// FIXME | ||
| project: WorkspaceMemberProject, |
There was a problem hiding this comment.
nit: It's a little weird to have project.project, e.g., to access the inner Project. It hints the naming might not be quite right.
| #[derive(Debug, Clone, PartialEq)] | ||
| #[cfg_attr(test, derive(serde::Serialize))] | ||
| pub struct WorkspaceMember { | ||
| struct WorkspaceMemberProject { |
There was a problem hiding this comment.
This type only exists for collect_members_only, right? Could we just declare this type locally and keep the public WorkspaceMember flat?
This draft PR contains a first pass at building a graph of workspace member dependencies to differentiate members that are graph sources from members that are dependencies. This is for checking whether we should build a member by default (see #14663).