rust-lang / cargo

The Rust package manager
https://doc.rust-lang.org/cargo
Apache License 2.0
12.74k stars 2.42k forks source link

Platform target-specific features #1197

Open alexcrichton opened 9 years ago

alexcrichton commented 9 years ago

It would be nice to support target-specific features for cases such as when certain functionality is only available on one platform or the default set of functionality should vary per-platform.

f32by commented 1 year ago

Hope for this to be supported. I have a workspace which contains a base crate depended by other crates and it has some platform-specificed features. Previously I have to write many times target.'cfg(blabla)'.dependencies to enable platform-specificed in those crates. Now I write a simple wrapper crate to re-export from the base crate however it still would be great to allow target.'cfg()'.features in Cargo.toml...

rivy commented 1 year ago

@f32by

Hope for this to be supported. I have a workspace which contains a base crate depended by other crates and it has some platform-specificed features. Previously I have to write many times target.'cfg(blabla)'.dependencies to enable platform-specificed in those crates. Now I write a simple wrapper crate to re-export from the base crate however it still would be great to allow target.'cfg()'.features in Cargo.toml...

Do you have any specific example you'd like to share? I'm curious what that code looks like... 👀

kajacx commented 1 year ago

What is the status of this request? I have following use case: I am making a crate that would unify the API between wasmtime and js-sys when it comes to loading WASM modules. I'm "switching" between these two implementations based on the build target, like this:

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
wasmtime = { version = "10.0.0" }

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = { version = "0.3.64" }

Now, let's say I want to add a feature, like component-model. This feature already exists in wasmtime, but I need to implement it manually when using js-sys. What I want is this:

[target.'cfg(not(target_arch = "wasm32"))'.features]
component-model = ["wasmtime/component-model"]

[target.'cfg(target_arch = "wasm32")'.features]
component-model

This way, when building for desktop, the component-model feature will enable wasmtime's component-model model, and when building for wasm, it will enable custom code inside the crate via the cfg!(feature) macro.

Is there currently any way to do this?

madsmtm commented 1 year ago

@kajacx since you already specify the dependency conditionally, I'm pretty sure you can just do:

[features]
component-model = ["wasmtime/component-model"]

And the wasmtime/component-model feature will only be enabled when the wasmtime dependency is present.

kajacx commented 1 year ago

@kajacx since you already specify the dependency conditionally, I'm pretty sure you can just do:

[features]
component-model = ["wasmtime/component-model"]

And the wasmtime/component-model feature will only be enabled when the wasmtime dependency is present.

I tried that with wasmtime?/component-model, but that gave the error that wasmtime isn't an optional dependency. Surprisingly, it works without the question mark - cargo will just silently ignore the "wasmtime/component-model" dependency if wasmtime doesn't exists.

darconeous commented 1 year ago

Just throwing in my own +1 to this.

The only real use case I need this for is to be able to specify different default dependencies depending on the target architecture. This would help improve ergonomics for new users, since it might not be obvious that the reason the build is failing is a known issue that is easily worked-around by removing a feature.

I had been hoping that this syntax would work, but of course, as anyone following this bug knows, it doesn't:

[target.'cfg(windows)'.features]
# Don't add the OpenSSL dependency on windows
"default" = []

[target.'cfg(not(windows))'.features]
"default" = ["openssl"]

I'd be happy with something that only worked to change the default features, but a more general solution (as hypothesized earlier in this issue) would be great, too.

spectria-limina commented 1 year ago

An important use case: disabling dynamic linking selectively on wasm targets, which do not support it. Dynamic linking is pretty important for fast rebuilds with large development projects, and is recommended by Bevy.

MOZGIII commented 1 year ago

A slight off-topic, but I think this works today:

[dependencies.bevy]
version = "0.11.0"
default-features = false
features = [
  // all cross-platform features
]

[target.'cfg(not(target_family = "wasm"))'.dependencies.bevy]
version = "0.11.0"
default-features = false
features = ["dynamic_linking"]

The downside is you have to specify the dependency version twice. The features are combined when non-wasm target is active.

tisonkun commented 1 year ago

@MOZGIII Thanks for your information! I verified it at https://github.com/apache/incubator-opendal/pull/2939.

And yes it's not optimal or at least weak documented.

UPDATE- No. It's documented at https://doc.rust-lang.org/cargo/reference/features.html#feature-unification

daxpedda commented 11 months ago

Just leaving a user-story here:

wgpu (a general purpose GPU library) supports multiple backends: GLES, DX11/12, Vulkan and Metal. Unfortunately they are not all equal, e.g. Vulkan is available on Linux and Windows, but on MacOS it requires MoltenVK (a compatibility layer).

Currently wgpu enables the Vulkan backend by default on Linux and Windows but not on MacOS. To let users disable those, we need features that can be disabled by using default-features = false but are enabled by default. So the solution here is to create two features, e.g. vulkan-native and vulkan-moltenvk. But both would use the same dependencies, so when using default = ["vulkan-native"] it would pull in all dependencies on MacOS as well, even though the desire here is to not enable Vulkan by default on MacOS.

Unfortunately the only workaround I'm aware of, is to create a crate, e.g. wgpu-default-features, that enables the desired crate features by default on each platform by using target.'cfg(...)'.dependencies and then using default = ["dep:wgpu-default-features"]. Not only is it pretty annoying to have to maintain a workaround crate, but in wgpu cfg guards now have to test for vulkan-native, vulkan-moltenvk and default.

aawsome commented 8 months ago

I'm also requesting this feature, but I also have a question about possible workarounds.

In my example, I want to add a mount subcommand which depends on fuse_mt. This dependency, however, only builds on certain targets, but we do want to have successful builds on all targets for all features (we test using --all-features) . Is there a way to achieve this without target-specific features?

epage commented 8 months ago

What would most help this conversation is for someone to go through and summarize

For example, from my quick skim, there are two workarounds (depending on the use case)

Neither helps with the "default backend" use case. I suspect in most cases that would be better served by global, mutually exclusive features and people should raise that there (if it isn't already addressed). If there is a use case where features is needed, rather than "global, mutually exclusive" features, then that should be called out here.

Based on my skimming and my above notes, I don't see there being anything left here which is why summarizing this is important so we can highlight what exists and help people know to call out what gaps remain that are affecting people.

daxpedda commented 8 months ago

Neither helps with the "default backend" use case. I suspect in most cases that would be better served by global, mutually exclusive features and people should raise that there (if it isn't already addressed). If there is a use case where features is needed, rather than "global, mutually exclusive" features, then that should be called out here.

Unfortunately I'm not very familiar with all the different "defalt backend" use cases out there, but for the Wgpu use-case I described in https://github.com/rust-lang/cargo/issues/1197#issuecomment-1837113787 "global, mutually exclusive" features wouldn't solve the problem (AFAIU).

epage commented 8 months ago

Why is that? Backends are a top-level concern so it seems like something that should be set using globals, rather than features which are for direct dependents and unified.

daxpedda commented 8 months ago

Why is that? Backends are a top-level concern so it seems like something that should be set using globals, rather than features which are for direct dependents and unified.

This is true and would also be a good fit for Wgpu but addresses a different problem.

Taking from https://github.com/rust-lang/cargo/issues/1197#issuecomment-1837113787 Wgpu needs:

Specifically the proposal mentions but leaves out:

Maybe I didn't understand the proposal well enough or missed something?


On second thought: maybe I misunderstood your earlier comment and you meant to say that this problem should rather be solved by "global, mutually exclusive features" because these use-cases should move away from crate features anyway?

epage commented 8 months ago

Taking from https://github.com/rust-lang/cargo/issues/1197#issuecomment-1837113787 Wgpu needs:

Are those for cases that should be controlled by the direct dependent or the final artifact?

daxpedda commented 8 months ago

Taking from #1197 (comment) Wgpu needs:

Are those for cases that should be controlled by the direct dependent or the final artifact?

  • If its for the direct dependent, this is the appropriate issue but an explanation why this is for direct dependents would be helpful.
  • If its for the final artifact, that would be feedback on the global, mutually exclusive features proposal.

They should be controlled by the final artifact. I will make a post there, thanks!

fwcd commented 6 months ago

Haven't read the whole thread, but this is my use case:

I am building a cross platform GUI library (nuit) and would like to provide both a smooth experience for library consumers that "just want sensible defaults" (e.g. SwiftUI on macOS, GTK+ on Linux, ...), while simultaneously offering the flexibility to select a custom backend. Since backends may be supported on multiple platforms (e.g. GTK+ runs on macOS too), I don't think this can cleanly be modeled with the winit workaround of enabling all backends by default and making them a no-op on unsupported platforms, since that would e.g. always result in GTK+ libraries being pulled in on macOS.

What I would really like to write would be something along the lines of

[target.'cfg(target_os = "macos")'.features]
default = ["swiftui"]

[target.'cfg(target_os = "linux")'.features]
default = ["gtk"]

which is very close to the existing suggestions from this thread. The other workaround would be introducing wrapper crates for each backend and using the existing target-specific dependency mechanism, but that still feels like an artificial restriction over something that could be expressed a lot more cleanly and concisely.