rust-lang/cargo

Platform target-specific features

alexcrichton opened this issue Β· 68 comments

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.

nox commented

Shouldn't this work?

[target.'cfg(not(any(target_os = "android", target_os = "windows")))'.dependencies]
ipc-channel = {git = "https://github.com/servo/ipc-channel"}

[target.'cfg(any(target_os = "android", target_os = "windows"))'.dependencies]
ipc-channel = {git = "https://github.com/servo/ipc-channel", features = ["inprocess"]}

@nox in theory yeah that seems like it should work, but today due to the structure of Resolve it doesn't. The entire target-independent resolution graph is created and then only later filtered down to the relevant set of packages. As an artifact of creating Resolve at the beginning it resolves all features, basically with a map from package to list of features. That means that all instances of ipc-channel in your example would have the inprocess feature enabled, regardless of what platform it was on.

I think that's a fixable bug, however, although likely quite invasive as it would require deferring calculation of features to the compilation phase rather than the resolution phase.

Ran into this extremely confusing bug today...

Would this also allow specifying a target-specific features.default array?
Something like:

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

[target.'cfg(target_os = "windows")'.features]
default = ["winonly_replacement"]

I suppose a workaround to get this would be an intermediate crate which depends on the actual crate with target-dependent features, but it doesn't sound very convenient.

@gyscos that seems like a nifty and natural way to encode this information! I haven't though too much about the implications of target-specific features, but I don't particularly see any immediate reason to block anything.

nox commented

This isn't enough, I don't want my crate to stop working because someone used no-default-features.

I guess one could use @retep998's trick of enabling a feature programatically from a build.rs script.

@nox That enables features in code programmatically. It doesn't let me control dependencies, but for some people just controlling their code might be enough. The reason I even wrote that trick was due to the lack of support for cyclic features. https://github.com/retep998/winapi-rs/blob/dev/build.rs#L193

philn commented

What's the status of this issue? It would be interesting to use target-specific features in gecko-media (enable pulseaudio only for linux for instance).

@philn AFAIK no progress has been made

Hope we can see progress on this issue...

This will help a lot with rust-random/rand#442

So is this a bug ( the [target.'cfg(target_os = "android")'.dependencies] / target.'cfg(target_os = "android")'.features notation should work, but it does not ) or a feature that requires a RFC?

@plyhun it's intended behavior that it's not supported, but it's a bug if Cargo doesn't warn you about unused keys. I'm not sure about whether or not adding the functionality requires an RFC, but if it becomes semi-large than I think it might!

is there any work-around for this besides creating an intermediate crate which depends on the actual crate? - that's very ugly.
Especially for code targeting wasm this could be very helpful.
Thanks

Making [target.'cfg(…)'.**] work for everything, rather than just [dependencies], seems to me like it should be a straightforward and sensible change to make. I can’t see any downsides to it. Am I missing anything?

I naively expected [target.'cfg(…)'.**] to work for everything. I guess I'd be great to if this was the reality - after if I expected it other people might've too.

That said, it might make sense to enable it just for the features - it's the safer way, because we might not want to allow that for [package] ([target.'cfg(…)'.package]) for example.

My use case is to write something like this:

[dependencies.tui]
version = "..."
default-features = false
optional = true

[dependencies.termion]
version = "..."
optional = true

[dependencies.crossterm]
version = "..."
optional = true

[features]
crossterm_backend = ["crossterm", "tui", "tui/crossterm"]
termion_backend = ["termion", "tui", "tui/termion"]

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

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

I don't think this can currently be achieved, unfortunately. :( The tricky part is passing the features to dependency conditionally. Is there some way I can do it today?

Can someone please explain how to work around this with a dummy crate? I tried, but I always end up with the feature enabled. My question on the forum has all the details of what I did, but no-one answered it yet.

Just ran into the bug that target specific dependency features are applied also for other targets :( Is there any chance of getting this bug fixed?

ehuss commented

Target-specific features for dependencies has been implemented and is available as a nightly-only feature on the latest nightly 2020-02-23. See the tracking issue at #7914.

If people following this issue could try it out, and leave your feedback on the tracking issue (#7914), I would appreciate it. Particularly we'd like to know if it helps your project and does it cause any breakage?

I'm going to leave this issue open, for the request of also changing the default features.

Hello,
what's the status of this issue? Is there still hope for it to see the light of day (and stabilization)?

features for dependencies has been implemented and is available as a nightly-only feature on the latest nightly 2020-02-23. See the

From what I can tell that only implements target specific dependencies, not target specific features.
So:

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

[target.'cfg(target_os="macos")'.features]
default-backend = ["mac"]

[target.'cfg(target_os="windows")'.features]
default-backend = ["win"]

Still doesnt work while I would expect it to work given that this this PR is supposed to solve this (github) issue.

I got a working draft of this, its currently one big commit over at https://github.com/JAicewizard/cargo/tree/target_specific_features.

@ehuss could you help me with getting this into cargo? There is probably an RFC process, but I think this issue shows good support for this already. I think its more important to the code into a state where it can be put into cargo. Feature flags, stabilisation process etc.

Code is definitely not clean ATM, I plan on splitting it up into different commits tomorrow.

ehuss commented

@JAicewizard I think it would be good to start by writing some clear motivations and use cases. Alex can't remember why he opened this issue, and we couldn't think of any use cases that aren't already solvable by existing means.

The prime use case I would have is, to have a feature that is not-mandatory for functionality, but it only works on certain OSes, i.e. jemalloc-ctl which is only viable on unix platforms when used with jemalloc, where it should be enabled by default, but not on windows. So that'd require target dependent default features or a similar mechanism. The approach as shown in #1197 (comment) does not work with 1.54.0 stable.

I am running into the same issue. I am currently writing a dll injector, as a library. I plan to implement it on all platforms. On windows I need some windows specific crates. On Linux I'll need other crates.
The way, things are currently, I'd just end up compiling all libraries, if I don't wanna implement target-specific features for every os.

I do not wanna add target-specific features, the user has to supply, because my goal is, to make the library, that I am writing, completely platform independent, to the one using it.
We have target specific modules in rust, but we do not have any way, to define dependencies, for those target specific modules.

When building a cross-platform application, sometimes I want FFI bindings to download/build/statically link the foreign library automatically on Windows, but use the system package on Linux, where installing stuff doesn't suck as bad. This often maps to wanting to enable a feature to bundle the foreign library on Windows only.

My use case is for druid which is a GUI toolkit. My design for backend selection relies on features, and we need one (or more) default backend(s) for each target OS that are compiled into the binary. This would allow someone to use a GTK backend on windows or mac for example, same for X11 (using some X11 compatibility layer). This would mostly just be for experimentation, but it doesnt have to be.

Even outside that, it would allow more clean cargo.tomls + code communication. You dont need complex cargo.tomls with lots of targets each with 5 features, instead you have 1 feature for each target each including their optional dependencies. Then the existance of these dependencies can be easily checked by locking it behind the feature that enables these dependencies. This is much cleaner and allows for easier feature management.

I think the current syntax makes this more confusing that it needs to be. The current [features] section does two things: it declare what the features are, and specifies the dependencies between features.

I think based on what the others say there is good need for target-specific dependencies. Note that this includes target specific default features as they are target-specific dependencies of the "default" feature.

However, there is no need for the mere existence of features to vary between platforms. It would be much better to solve that with something like Cabal's "buildable: false". Not only can that nicely express not all features work everywhere, it also can be used to express that certain features are incompatible wihout violating the additivity of features! (Technically, one can express "buildable: false" today with an impossible depenency, so we know it's safe. This just takes that existing functionality and makes it more ergonomic.)

This, and weak deps as I described in rust-lang/rfcs#3143, are both cases of things we can't get today, but a more flexible language for specifying dependencies would get is for free. I really hope we can get that more flexible language!

CC @Eh2406

I think the current syntax makes this more confusing that it needs to be. The current [features] section does two things: it declare what the features are, and specifies the dependencies between features.

I disagree, it is in line with the target specific dependency declaration so imho it's consistent.

I think based on what the others say there is good need for target-specific dependencies. Note that this includes target specific default features as they are target-specific dependencies of the "default" feature.

However, there is no need for the mere existence of features to vary between platforms. It would be much better to solve that with something like Cabal's "buildable: false". Not only can that nicely express not all features work everywhere, it also can be used to express that certain features are incompatible wihout violating the additivity of features! (Technically, one can express "buildable: false" today with an impossible dependency, so we know it's safe. This just takes that existing functionality and makes it more ergonomic.)

Good catch! Couldn't this be solved by an impl detail to check if a feature exists at all before checking if it exists for the current target, and show a sufficiently detailed error message? Iiuc that would be equal to an explicit buildable flag. Could you reference where this is outlined? Thanks!

However, there is no need for the mere existence of features to vary between platforms

I dont think that is true. We can already request optional dependencies (which are features) form another platform to be included, and this works on stable. I think my implementation also has a similar behaviour to these dependencies. On targets where the features dont exist, they are just ignored/are empty.

It would be much better to solve that with something like Cabal's "buildable: false".

That would require the users to select the features, I want to give the user one stable API on diferent platforms without the users having to ask for a diferent feature on each platform.

@JAicewizard

We can already request optional dependencies (which are features) form another platform to be included, and this works on stable.

Requesting conditionally is perfectly fine. It's providing features optionally to downstream creates conditionally that's suspicious.

That would require the users to select the features, I want to give the user one stable API on diferent platforms without the users having to ask for a different feature on each platform.

That is what the conditional default features do. Isn't that what you want?

@drahnr

I disagree, it is in line with the target specific dependency declaration so imho it's consistent.

[target."....".features] is consistent with [target."....".dependencies], but only the former has the issue I mention I believe? It's precisely the fact that doing the consistent runs into issues that means we should perhaps back up and fine something more different ("a more flexible language for specifying dependencies") so we can be consistent and have good semantics.

Good catch! Couldn't this be solved by an impl detail to check if a feature exists at all before checking if it exists for the current target, and show a sufficiently detailed error message? Iiuc that would be equal to an explicit buildable flag.

That somewhat works, but i don't think is as clean. What we want to express is "Foo depends on macOS or Linux", not "If mac or linux exists there is a Foo". I think the framing really matters as to what users end up writing and how they feel about it.

Could you reference where this is outlined? Thanks!

https://cabal.readthedocs.io/en/3.4/cabal-package.html?highlight=buildable%3A#pkg-field-buildable. The docs mention it in conjunction with an autoconf-ish setup, but it is more commonly used these days just with regular conditional deps, such as in https://github.com/reflex-frp/reflex-dom/blob/master/reflex-dom/reflex-dom.cabal (which incidentally I think is pretty close to @JAicewizard's use-case).

@Ericson2314

Requesting conditionally is perfectly fine. It's providing features optionally to downstream creates conditionally that's suspicious.

I dont think you understand what I mean. Currently we can already provide features that are target specific.

[target."cfg(target_os=linux)".dependencies]
dep1 = {version = x, optional = true}

@JAicewizard can a downstream create refer to dep1? I thought dependencies overloaded features as if they were "private" features, since the crate can refer to its own optional dependencies but not a downstream crate.

I agree in that this is no good, but if it doesn't like into the crate's public (cargo-side) interface, then it is at least local and could be fixed with an edition.

@Ericson2314 Yes, if you are on a target with no explicitly listed dependency with that name it will just ignore it. At least from my testing.

@JAicewizard say that again? In downstream crates I would expect trying to refer to any dependency in an upstream crate (that doesn't share a name with a feature in which case we are referring to the feature) to be an error, not silently ignored.

I take it you mean within the same crate declaring the target-specific dependency?

I believe I have a use-case for this. I am working on a cross-platform tool that needs to interact with a USB device. I use libusb via rusb to achieve the majority of the abstraction. On Windows, I need a special, patched version of libusb to get around a limitation. I therefore enable the vendored feature on the libusb1-sys crate, which builds and statically links libusb.

My dependencies are as follows:

[workspace]
[package]
# ...
edition = "2018"

[dependencies]
rusb = "0.8.1"
# ...

[target.'cfg(windows)'.dependencies]
libc = "0.2"
windows = "0.17.2"
win32_bindings = { path = "src/backend/windows/win32_bindings" }
libusb_internals = { path = "src/backend/windows/libusb_internals" 

where libusb_internals itself has the following dependencies:

[dependencies]
libusb1-sys = { version = "0.5.0", features = ["vendored"] }
win32_bindings = { path = "../win32_bindings" }

[build-dependencies]
cc = "1.0"

On other platforms, I would rather dynamically link libusb, however, the "vendored" feature appears to be picked up and libusb is being built. I also see libusb_interals-* folders in target/debug/build/ which makes sense given that target-specific dependencies are only filtered later in the process. All this wouldn't be a huge problem if it weren't for the fact that libusb1-sys apparently doesn't set up the correct environment on (older versions of) macOS and fails to build libusb. I can manually build libusb or install it from Homebrew without problem, though. Manually commenting out the Windows-specific libusb_internals dependency lets my project build correctly on macOS.

This leaves me at an impasse where it seems impossible to configure my project correctly for multiple target platforms.

Somewhat related: Cargo.lock currently tracks dependencies/features across all build targets which can be a problem.

Here's an example: I have an codebase I build for WebAssembly and as a native binary. In the latter case I use the rand crate with std and getrandom.
However, getrandom does not support WebAssembly so it won't compile, even when I explicitly disable all default features on the wasm target.

CryZe commented

@kaimast Are you using Rust 2021 and / or resolver 2? This should not be happening if either of those are the case. (Unless of course a crate legitimately pulled in by WASM does indeed activate that feature)

Although wait, what do you even mean? getrandom does support WASM, you actually need to activate a feature for it to work, not vice versa.

If anyone is still searching, I've been able to work around this problem by using a combination of features and a cargo-make script: https://github.com/espanso/espanso/pull/1287/files

My use-case was very similar to the one by @Cu3PO42, as I wanted to enable/disable certain reqwest features based on the OS

What’s the status on this?

Where should someone look to try and make this work?

Have there been PRs trying to do something about it?

Cheers

See #1197 (comment) for more information, basically the team forgot why this would be useful and didn't really have any intentions of pursuing this any further. But I have a probably working branch implementing this, just for an older version of cargo.

Would love to have this as well!
I actually have a pretty good usecase right now.

I'm trying to create a shared lib crate used by

  • a Rust project using the esp32 toolchain
  • a Android project via FFI using the armv7 target

To enable the FFI logic, we utilize a code generator crate.
When using a target specific dependency via [target.armv7-linux-androideabi.dependencies] to include this crate, the whole dependency graph of that dependency is also included in the esp related project. This then messes things up resulting in a compilation error, as the project's resolved dependency versions change due to this new dependency. I suspect that some std features are included, which don't work on the esp32 toolchain.

Long story short, the only thing that worked was hiding those FFI crates behind a feature flag.

It would be great if one would be now enable to enable feature flag based on that specific target.

Canop commented

If what blocks progress here is the lack of use cases, here's mine.

broot has a clipboard feature, which works on some targets and doesn't on some other ones. At various point in the code, I must check whether the feature is available. Checking both the feature and the combination of targets, and updating those checks everywhere when there's progress in the feature implementation on various targets would be a mess.
Right now, users can't do a simple cargo install, they must, depending on their platform, add --features clipboard themselves.

fogti commented

@Canop couldn't that be handled by doing this target-to-feature inference once in build.rs? Obvious downside would be that it is not as declarative (but I'd assumed this feature to be more about cases where a feature wouldn't be reasonably possible to implement, e.g. io_uring support on something that isn't linux, or such).

Canop commented

@fogti You mean by outputting cargo:rustc-cfg=clipboard in build.rs ? That's honestly something I didn't think about (and didn't know possible, thanks). This might work but the fact that Cargo conditional dependencies aren't affected makes it messy again (but at least not everywhere in the code).

@fogti You mean by outputting cargo:rustc-cfg=clipboard in build.rs ? That's honestly something I didn't think about (and didn't know possible, thanks). This might work but the fact that Cargo conditional dependencies aren't affected makes it messy again (but at least not everywhere in the code).

I think you could also check if the feature is enabled in the build script and error out or give a warning if the feature should be enabled

Adding another use-case. One of our dependencies has a feature that links against a specific library that we'd like to enable for a specific os. Currently, we're passing --features crate/feature on that one specific OS, but it'd be nice to handle this in Cargo.toml

AFAIK, the syntax in #1197 (comment) works currently, but the one that is still lacking is the one from #1197 (comment).

f32by commented

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

@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

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?

@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

@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.

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.

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.

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.

@MOZGIII Thanks for your information! I verified it at apache/opendal#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

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.

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?

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

  • Use cases
  • Workarounds
  • Proposed solutions

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

  • Making a target dependency optional and referring to it in a feature (example)
  • Declaring a regular dependency with the base-level features and then duplicating that in a target dependency with additional features (example)
    • This duplicates the source but that can be reduced with workspace inheritance

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.

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 #1197 (comment) "global, mutually exclusive" features wouldn't solve the problem (AFAIU).

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.

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 #1197 (comment) Wgpu needs:

  • To enable some dependencies (and features) by default depending on the target.
  • Expose crate features that do nothing depending on the target.

Specifically the proposal mentions but leaves out:

  • Target-specific set-globals, which would have to play well with default as well.
  • Multi-valued globals, which Wgpu could probably live without, but would still introduce some complexity, having to play around with multiple globals that do the same thing depending on the target or if using the default.

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?

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.

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

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.