Skip to content

URL Dependency Issue in Workspace Packages with Build Dependencies on Other Workspace Packages #19074

@Panacea729

Description

@Panacea729

Summary

In a project that only uses workspace packages, I get this error when building a workspace package which has a build-dependency on another workspace package which has a dependency on another workspace package. In my example, my-tool lists my-backend as a build-dependency. my-backend depends on my-util.

$ uv build --wheel --package my-tool
Building wheel...
  × Failed to build `/home/user/uv-testing/my-workspace/my-tool`
  ├─▶ Failed to resolve requirements from `build-system.requires`
  ├─▶ No solution found when resolving: `my-backend @ file:///home/user/uv-testing/my-workspace/my-backend`
  ├─▶ Failed to resolve dependencies for package `my-backend==0.1.0`
  ╰─▶ Package `my-util` was included as a URL dependency. URL dependencies must be expressed as direct requirements or
      constraints. Consider adding `my-util @ file:///home/user/uv-testing/my-workspace/my-util` to your dependencies
      or constraints file.

I cannot tell whether this is a bug or something that is covered by https://docs.astral.sh/uv/reference/internals/resolver/#url-dependencies. To me, it seems as though depending on workspace packages doesn't count as an "index package" and isn't subject to the same issues that resolving index packages have (since we directly list workspace = true as the source of the package). Since neither my-util nor my-backend is included as a dependency in my-tool's wheel, it seems wrong to enforce these resolution requirements onto building.

The docs state,

uv requires that URLs are either declared directly ... or by other URL dependencies.

In the error it seems as though my-backend is attempting to be resolved as a URL dependency (I guess something happens in uv which takes a workspace package and transforms it to a URL dependency) and hence it should be OK for it to include other url dependencies.

Reproducible Pyprojects

The pyproject's for the example are as follows:

my-workspace/pyproject.toml

[project]
name = "my-workspace"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[tool.uv]
package = false

[tool.uv.workspace]
members = ["my-util", "my-backend", "my-tool"]

[tool.uv.sources]
my-backend = { workspace = true }
my-util = { workspace = true }

my-workspace/my-util/pyproject.toml

[project]
name = "my-util"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

my-workspace/my-backend/pyproject.toml

[project]
name = "my-backend"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = ["my-util"]

my-workspace/my-tool/pyproject.toml

[project]
name = "my-tool"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[build-system]
requires = ["my-backend", "setuptools"] # note setuptools is included here since I didn't actually make a real backend for this example, but it should be irrelevant to this issue

Workarounds

The most succinct workaround is to have my-tool declare my-util as a build-dependency. I.e.

my-workspace/my-tool/pyproject.toml

[project]
name = "my-tool"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[build-system]
requires = ["my-backend", "setuptools", "my-util"] 

In my circumstance, I have a really large workspace with 100s of workspace packages which all depend on my-backend or equivalent sort of packages (i.e. plugins). These plugins can be changed often and their dependencies on other local workspace packages can also be changed. It is a pain to go through every dependent workspace package to update their build dependencies with the new transitive dependencies that are necessary for the build plugins it wants to use. There should be no need to have to update transitive dependencies.

These are some other workarounds I've started using that don't involve constantly updating workspace packages with transitive dependencies.

For uv build commands specifically, my workaround is using a build-constraints file like this:

echo "my-util @ file:///home/user/uv-testing/my-workspace/my-util" > my-constraints.txt
uv build --wheel --build-constraints my-constraints.txt --package my-tool

Essentially just have all the common util-like packages that build plugins depend on in a constraints file and use that constraints file for every package you build.

In cases where uv sync requires actually calling the backend of affected files, it also fails. For example, let's include my-tool into a dependency group so that it'll sync when we sync the root project.

my-workspace/pyproject.toml

[project]
name = "my-workspace"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[tool.uv]
package = false

[tool.uv.workspace]
members = ["my-util", "my-backend", "my-tool"]

[dependency-groups]
tools = ["my-tool"]

[tool.uv.sources]
my-tool = { workspace = true }
my-backend = { workspace = true }
my-util = { workspace = true }
$ uv sync --group tools
Resolved 4 packages in 2ms
  × Failed to build `my-tool @ file:///home/user/uv-testing/my-workspace/my-tool`
  ├─▶ Failed to resolve requirements from `build-system.requires`
  ├─▶ No solution found when resolving: `setuptools`, `my-backend @
  │   file:///home/user/uv-testing/my-workspace/my-backend`
  ├─▶ Failed to resolve dependencies for package `my-backend==0.1.0`
  ╰─▶ Package `my-util` was included as a URL dependency. URL dependencies must be expressed as direct requirements or
      constraints. Consider adding `my-util @ file:///home/user/uv-testing/my-workspace/my-util` to your dependencies
      or constraints file.
  help: `my-tool` was included because `my-workspace:tools` (v0.1.0) depends on `my-tool`

My workaround for this is using build-constraint-dependencies. It seems like this ends up resolving my-util prior to the build-system getting mad that my-backend is using a URL dependency.

[tool.uv]
build-constraint-dependencies = ["my-util"]

Platform

Linux

Version

uv 0.11.7 (x86_64-unknown-linux-gnu)

Python version

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions