Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect documentation for default-features in workspace dependencies #14841

Open
konstin opened this issue Nov 20, 2024 · 2 comments
Open

Incorrect documentation for default-features in workspace dependencies #14841

konstin opened this issue Nov 20, 2024 · 2 comments
Labels
A-documenting-cargo-itself Area: Cargo's documentation A-features Area: features — conditional compilation A-manifest Area: Cargo.toml issues A-workspace-inheritance Area: workspace inheritance RFC 2906 S-needs-design Status: Needs someone to work further on the design for the feature or fix. NOT YET accepted.

Comments

@konstin
Copy link
Contributor

konstin commented Nov 20, 2024

The cargo docs on workspace packages say (https://doc.rust-lang.org/nightly/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace):

Along with the workspace key, dependencies can also include these keys:

  • optional: Note that the[workspace.dependencies] table is not allowed to specify optional.
  • features: These are additive with the features declared in the [workspace.dependencies]

Other than optional and features, inherited dependencies cannot use any other dependency key (such as version or default-features).

Trying this out however, cargo does not seem to mind the default-features key:

[workspace]
members = ["workspace-member"]
resolver = "2"

[workspace.dependencies]
flate2 = { version = "1.0.35", default-features = false }
url = { version = "2.5.3", features = ["serde"] }
[package]
name = "workspace-member"
version = "0.1.0"
edition = "2021"

[dependencies]
flate2 = { workspace = true, default-features = true }
url = { workspace = true, default-features = false }

It's unfortunately not clear to me from the docs if default-features are inherited.

konstin added a commit to astral-sh/uv that referenced this issue Nov 20, 2024
When building only a single crate in the workspace to run its test, we often recompile a lot of other, unrelated crates. Whenever cargo has a different set of crate features, it needs to recompile. By moving some features (non-exhaustive for now) to the workspace level, we always activate them an avoid recompiling.

The cargo docs mismatch the behavior of cargo around default-deps, so I filed that upstream: rust-lang/cargo#14841

Reference script:

```python
import tomllib
from collections import defaultdict
from pathlib import Path

uv = Path("/home/konsti/projects/uv")
skip_list = ["uv-trampoline", "uv-dev", "uv-performance-flate2-backend", "uv-performance-memory-allocator"]

root_feature_map = defaultdict(set)
root_default_features = defaultdict(bool)
cargo_toml = tomllib.loads(uv.joinpath("Cargo.toml").read_text())
for dep, declaration in cargo_toml["workspace"]["dependencies"].items():
    root_default_features[dep] = root_default_features[dep] or declaration.get("default-features", True)
    root_feature_map[dep].update(declaration.get("features", []))

feature_map = defaultdict(set)
default_features = defaultdict(bool)
for crate in uv.joinpath("crates").iterdir():
    if crate.name in skip_list:
        continue
    if not crate.joinpath("Cargo.toml").is_file():
        continue
    cargo_toml = tomllib.loads(crate.joinpath("Cargo.toml").read_text())
    for dep, declaration in cargo_toml.get("dependencies", {}).items():
        # If any item uses default features, they are used everywhere
        default_features[dep] = default_features[dep] or declaration.get("default-features", True)
        feature_map[dep].update(declaration.get("features", []))

for dep, features in sorted(feature_map.items()):
    features = features - root_feature_map.get(dep, set())
    if not features and default_features[dep] == root_default_features[dep]:
        continue
    print(dep, default_features[dep], sorted(features))
```
@weihanglo
Copy link
Member

Documenting is hard ;(
This is something we've tried to make it clear. See https://doc.rust-lang.org/nightly/edition-guide/rust-2024/cargo-inherited-default-features.html and #12162 (comment).

Any idea how to add a succinct explanation to the doc?
cc @Muscraft

@weihanglo weihanglo added A-features Area: features — conditional compilation A-documenting-cargo-itself Area: Cargo's documentation A-manifest Area: Cargo.toml issues S-needs-design Status: Needs someone to work further on the design for the feature or fix. NOT YET accepted. A-workspace-inheritance Area: workspace inheritance RFC 2906 labels Nov 20, 2024
@konstin
Copy link
Contributor Author

konstin commented Nov 20, 2024

Something like "A workspace member will inherit default-features = false if set in the workspace root, but it can't deactivate default features if they weren't deactivated by the workspace root". I found the table in #12162 (comment) very helpful.

konstin added a commit to astral-sh/uv that referenced this issue Nov 20, 2024
When building only a single crate in the workspace to run its tests, we
often recompile a lot of other, unrelated crates. Whenever cargo has a
different set of crate features, it needs to recompile. By moving some
features (non-exhaustive for now) to the workspace level, we always
activate them an avoid recompiling.

The cargo docs mismatch the behavior of cargo around default-deps, so I
filed that upstream and left most `default-features` mismatches:
rust-lang/cargo#14841.

Reference script:

```python
import tomllib
from collections import defaultdict
from pathlib import Path

uv = Path("/home/konsti/projects/uv")
skip_list = ["uv-trampoline", "uv-dev", "uv-performance-flate2-backend", "uv-performance-memory-allocator"]

root_feature_map = defaultdict(set)
root_default_features = defaultdict(bool)
cargo_toml = tomllib.loads(uv.joinpath("Cargo.toml").read_text())
for dep, declaration in cargo_toml["workspace"]["dependencies"].items():
    root_default_features[dep] = root_default_features[dep] or declaration.get("default-features", True)
    root_feature_map[dep].update(declaration.get("features", []))

feature_map = defaultdict(set)
default_features = defaultdict(bool)
for crate in uv.joinpath("crates").iterdir():
    if crate.name in skip_list:
        continue
    if not crate.joinpath("Cargo.toml").is_file():
        continue
    cargo_toml = tomllib.loads(crate.joinpath("Cargo.toml").read_text())
    for dep, declaration in cargo_toml.get("dependencies", {}).items():
        # If any item uses default features, they are used everywhere
        default_features[dep] = default_features[dep] or declaration.get("default-features", True)
        feature_map[dep].update(declaration.get("features", []))

for dep, features in sorted(feature_map.items()):
    features = features - root_feature_map.get(dep, set())
    if not features and default_features[dep] == root_default_features[dep]:
        continue
    print(dep, default_features[dep], sorted(features))
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-documenting-cargo-itself Area: Cargo's documentation A-features Area: features — conditional compilation A-manifest Area: Cargo.toml issues A-workspace-inheritance Area: workspace inheritance RFC 2906 S-needs-design Status: Needs someone to work further on the design for the feature or fix. NOT YET accepted.
Projects
None yet
Development

No branches or pull requests

2 participants