Skip to content

feat: support uv workspaces in bentoml build (#5575) - #5677

Open
atharv0o wants to merge 1 commit into
bentoml:mainfrom
atharv0o:feature/uv-workspace-support-5575
Open

atharv0o wants to merge 1 commit into
bentoml:mainfrom
atharv0o:feature/uv-workspace-support-5575

Conversation

@atharv0o

Copy link
Copy Markdown

What does this PR address?

Fixes #5575

Adds support for uv workspaces in bentoml build.

The problem

When a project uses a uv workspace and declares a dependency via
tool.uv.sources = { workspace = true }, bentoml build currently fails
during uv pip compile with:
This happens because BentoML resolves dependencies from an isolated
temporary directory (env/python) that has no visibility into the parent
workspace's pyproject.toml or member layout.

The fix

This PR translates workspace-member dependencies into absolute local
filesystem paths before compilation, so uv can resolve and build them
natively from their real location — without needing to reconstruct
workspace discovery inside a temp build context.

Why this approach, and not running uv from the workspace root
directly:
that fails on Windows. Passing the root pyproject.toml
causes uv/Rust to canonicalize the path to a long UNC path (\\?\...),
while glob-expanded workspace members from that same file resolve as
standard Windows paths — the string comparison mismatches, producing
bird-feeder references a workspace in tool.uv.sources, but is not a workspace member even on a valid config. Translating members to absolute
paths sidesteps this entirely, since uv never needs workspace discovery
from inside the temp directory.

We also considered generating a temporary pyproject.toml mapping
workspace sources, but rejected it — it introduces a second, drift-prone
source of truth for workspace metadata.

Scope

This PR intentionally covers only dependencies explicitly marked
workspace = true in tool.uv.sources. Arbitrary/non-workspace local
path dependencies (e.g. a manually declared { path = "../ext" } outside
a workspace) are explicitly out of scope — see "Future work" below. This
was a deliberate choice to keep the review surface small.

Changes

src/bentoml/_internal/bento/build_config.py

  • find_workspace_root() — locates the workspace root by walking up for
    a pyproject.toml containing [tool.uv.workspace].
  • get_workspace_members() — resolves member packages to absolute paths,
    honoring tool.uv.workspace.exclude.
  • rewrite_if_workspace_member() — rewrites a workspace member
    requirement to its absolute path using
    packaging.requirements.Requirement, preserving extras and environment
    markers.
  • PythonOptions.write_to_bento() — discovers the workspace root/members
    from build_ctx and applies the rewrite when generating
    requirements.txt.
  • PythonOptions.fix_dep_urls() — detects local directory dependencies,
    validates each resolved path (via os.path.realpath, closing a
    symlink-escape gap) stays within the workspace root — raising
    BentoMLException otherwise — then delegates to build_local_dep().

src/bentoml/_internal/bento/bentoml_builder.py

  • build_local_dep() — builds a workspace member via uv build --sdist --out-dir <temp_dir> inside a tempfile.TemporaryDirectory, so no
    build artifacts are written into the user's source tree. Raises
    BentoMLException with the captured command, exit code, and stderr on
    build failure.

Security

  • Local dependency paths — including through symlinks — are validated
    against the workspace root using resolved (realpath) paths on both
    sides, closing a gap where a symlink inside the workspace could point
    outside it.
  • Build output is written only to a temporary directory, never the user's
    actual project directory.

Backward compatibility

No behavior change for projects not using uv workspaces. The new logic
only activates when a workspace root is discovered and a dependency
name matches a registered workspace member.

Testing

  • Ordinary PyPI dependency resolution (regression check)
  • Workspace member dependency resolution and packaging
  • Transitive workspace dependency resolution
  • tool.uv.workspace.exclude is honored
  • Extras and environment markers preserved through path rewriting
  • Path normalization on Windows and Unix
  • Local path dependency outside the workspace root → BentoMLException
  • Symlink inside the workspace pointing outside the root →
    BentoMLException (skipped on Windows without Developer Mode)
  • Zero-pollution check: source directory is unchanged before/after a
    workspace member is built and packaged

Future work (not in this PR)

  • Arbitrary local path dependencies declared outside a workspace context
  • Incremental rebuild/caching of workspace member sdists

Before submitting:

  • Does the Pull Request follow Conventional Commits specification naming?
  • Does the code follow BentoML's code style, pre-commit run -a script has passed?
  • Did you read through contribution guidelines and follow development guidelines?
  • Did your changes require updates to the documentation? Have you updated those accordingly?
  • Did you write tests to cover your changes?

Signed-off-by: atharv0o <missionblood2@gmail.com>
@atharv0o
atharv0o requested a review from a team as a code owner July 27, 2026 19:30
@atharv0o
atharv0o requested review from ssheng and removed request for a team July 27, 2026 19:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: uv Workspace Support

1 participant