bazelbuild / bazelbuild/rules_rust

crate_universe: local_crate_mirror does not accept relative paths despite docs implying it might

Open
#4,036 1 comment 0 reactions 0 assignees View on GitHub
crate-universe needs-design needs-triage
Dominant language
Starlark
Stars
843
Forks
651
Avg merge
2d 18h
Merged PRs (30d)
15

Description

## Summary

`crate_universe`'s `update-repos` (cargo-bazel renderer) emits `local_crate_mirror(path = "")` calls when consumers use a standard Cargo `[patch.crates-io]` entry with a relative path. But the `local_crate_mirror` repository rule cannot consume the path it was given: `repository_ctx.path(string)` resolves relative paths against the *external repo's own directory*, not the workspace root, so the `readdir()` call inside the rule fails immediately.

So the bug is entirely internal to rules_rust — the generator produces a value the consumer can't use.

## Reproducer

In a Bazel workspace, vendor a crate hand-tweak it, then patch it into Cargo:

```toml
# Cargo.toml
[patch.crates-io]
mycrate = { path = "third_party/rust/vendor/mycrate-1.0.0" }
```

```bash
bazel run //third_party/rust:update-repos
```

cargo-bazel renders this into `third_party/rust/crates/defs.bzl`:

```python
local_crate_mirror(
name = "crate_index__mycrate-1.0.0",
options_json = "{...,\"local_path\":\"third_party/rust/vendor/mycrate-1.0.0\",...}",
path = "third_party/rust/vendor/mycrate-1.0.0",
)
```

`crate_universe/src/metadata/metadata_annotation.rs:284–310` explicitly produces this workspace-relative form, and the in-code comment says:

> // This replacement allows in-repo patches sections to work as intended using local_crate_mirror.

So upstream's intent is clearly to support relative `[patch.crates-io]` paths. But `crate_universe/private/local_crate_mirror.bzl:9`:

```python
def _local_crate_mirror_impl(repository_ctx):
path = repository_ctx.path(repository_ctx.attr.path)
...
for child in repository_ctx.path(path).readdir():
...
```

`repository_ctx.path(relative_str)` resolves under the external repo's own directory. The external repo `@@_main~rust_crates_extension~crate_index__mycrate-1.0.0` doesn't contain `third_party/rust/vendor/mycrate-1.0.0`, so:

```
Error in readdir: can't readdir(), not a directory:
.../external/_main~rust_crates_extension~crate_index__mycrate-1.0.0/third_party/rust/vendor/mycrate-1.0.0
```

The `path` attribute docstring even has a TODO flagging this:

```python
"path": attr.string(
# TODO: Verify what happens if this is not an absolute path.
doc = "Absolute path to the BUILD.bazel file to generate.",
),
```

The docstring says "absolute" but the generator emits relative — that contradiction is the bug.

## Proposed fix

Make `local_crate_mirror` resolve `path` relative to the workspace root when relative:

```python
def _local_crate_mirror_impl(repository_ctx):
path = repository_ctx.workspace_root.get_child(repository_ctx.attr.path)
...
```

`repository_ctx.workspace_root` is well-defined and always points at the main workspace root from inside an external repo's fetch.

Either that, or update the renderer in `metadata/metadata_annotation.rs` to emit an absolute path (and update the `path` attr docs/checks accordingly). The first option seems cleaner because Cargo `[patch.crates-io]` paths are always relative-to-the-manifest by Cargo's design.

We carry the first option as a local patch against 0.70.0. Happy to send a PR if maintainers prefer.

## Versions

- rules_rust: 0.70.0
- bazel: 7.7.1

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.