astral-sh / uv

An extremely fast Python package and project manager, written in Rust.
https://docs.astral.sh/uv
Apache License 2.0
20.03k stars 595 forks source link

Workspace exclusion does not work #7071

Closed blinkseb closed 6 days ago

blinkseb commented 1 week ago

uv platform: Fedora 40 uv version: uv 0.4.5

Hello,

First of all, thanks a lot for uv, it's an awesome tool!

I'm trying to migrate my current project from poetry to uv by leveraging the workspace functionality. I have the following projects structure:

.
├── projects
│   ├── a
│   │   ├── hello.py
│   │   ├── pyproject.toml
│   │   └── README.md
│   ├── b
│   │   ├── ba
│   │   │   ├── hello.py
│   │   │   ├── pyproject.toml
│   │   │   └── README.md
│   │   └── bb
│   │       ├── hello.py
│   │       ├── pyproject.toml
│   │       └── README.md
│   └── c
│       ├── hello.py
│       ├── pyproject.toml
│       └── README.md
├── pyproject.toml

The root pyproject.toml, the workspace root, is defined as:

[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
dependencies = []

[tool.uv]
package = false

[tool.uv.workspace]
members = ["projects/*", "projects/b/*"]
exclude = ["projects/b"]

However, uv lock (or basically any uv command) fails this the following error message:

❯ uv lock
error: Workspace member `/home/sbrochet/uv-test/workspace/projects/b` is missing a `pyproject.toml` (matches: `projects/*`)

even if projects/b is explicitly excluded.

I've also tried to remove the members entry projects/b/*, but it fails with the same error message.

Thanks a lot again!

charliermarsh commented 1 week ago

Thanks, this looks like a bug. I'll take a look today.

charliermarsh commented 1 week ago

To clarify, do you want projects/a/ba to be included? Or everything under projects/b should be excluded?

blinkseb commented 1 week ago

Thanks, this looks like a bug. I'll take a look today.

Awesome thanks a lot!

To clarify, do you want projects/a/ba to be included? Or everything under projects/b should be excluded?

Ideally I'd like to have projects/b/ba included in the workspace since only projects/b is excluded.

charliermarsh commented 1 week ago

No prob. Is projects/b a project? Like does projects/b/pyproject.toml exist? Or it's just a directory that contains other projects? (I assume you also want projects/b/bb to be included?)

blinkseb commented 1 week ago

I'm sorry, I see how my example can be confusing. We have a monorepo where we have multiple projects stored in the projects folder. We then sub-categorize the projects per product, each into their own sub-folder. We also have cross-product project which sit at the root of the projects folder. So a more realistic layout would be the following

.
├── projects
│   ├── product-a
│   │   └── project
│   │       └── pyproject.toml
│   ├── product-b
│   │   └── project
│   │       └── pyproject.toml
│   └── project-a
│       └── pyproject.toml
├── pyproject.toml

Here product-a and product-b are only used to group projects together, they are not python project and do no have a pyproject.toml file. So I would except that the following uv pyproject.toml to work:

[tool.uv.workspace]
members = ["projects/*", "projects/product-a/*", "projects/product-b/*"]
exclude = ["projects/product-a", "projects/product-b"]
charliermarsh commented 1 week ago

Ok thank you, I think I follow now. I just tested and it looks like Cargo (which we modeled our workspace discovery after) doesn't support this kind of interaction either. It makes sense that it doesn't work if you consider that excludes are applied after member discovery, and we discover by walking over directories. Though I'd argue that the error message is wrong:

error: Workspace member `/Users/crmarsh/workspace/uv/projects/projects/product-a` is missing a `pyproject.toml` (matches: `projects/*`)

It should instead say that we can't find a workspace member for product-a if it's referenced somewhere else, or similar. We shouldn't be trying to find a project in projects/product-a, since it's excluded.

I think you'll need a slightly different structure to make this work...

One option is to avoid including "projects/*", like you could do:

[tool.uv.workspace]
members = ["projects/project-a", "projects/product-a/*", "projects/product-b/*"]

Or:

[tool.uv.workspace]
members = ["projects/project-*", "projects/product-a/*", "projects/product-b/*"]

(I assume they don't actually follow that pattern.)

Or you could put all the projects in a subdirectory of their own, like projects/projects/project-a?

charliermarsh commented 1 week ago

Ok, this layout actually works after #7175:

[project]
name = "projects"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
dependencies = []

[tool.uv]
package = false

[tool.uv.workspace]
members = ["projects/*", "projects/product-a/*", "projects/product-b/*"]
exclude = ["projects/product-a", "projects/product-b"]