Show target compatibility in documentation

Is it possible to check which targets a crate is compatible with, for example wasm32-unknown-unknown or wasm32-wasi? I am currently looking to make some crates I use compatible with WebAssembly, but it is kind of difficult to find the dependencies which cause compatibility issues, because they might be very deep down in the dependency tree and may have various versions. It would be useful to have a single view showing the compatibility of a crate and it's dependencies, recursively.

Is there a way to do this yet? Could something like this be added to the generated documentations eventually?

It would be useful. It may be tricky to do it reliably automatically.

You could run cargo check --target wasm32-unknown-unknown, but the result isn't a guarantee that the crate will/won't work.

When a build fails, it's tricky to tell whether it failed because it's fundamentally incompatible, or because it's not configured properly. Some crates may work if you disable incompatible Cargo features.

Some builds may fail due to wrong environment, e.g. sys crates with C dependencies can work with WASM, but you need to make them build from source and configure emcc just right.

And crates may syntactically work, but fail at run time when they use std::fs or std::thread. Rust doesn't support Cargo feature flags for std yet, but pretends to have std even for bare WASM.

1 Like

It would be nice to have fields in Cargo.toml to explicitly declare target compatibility or incompatibility. That would let cargo detect such issues without requiring a cfg-guarded compile_error!, and the same information could serve as documentation on crates.io and docs.rs.

3 Likes

Something like

[build]
targets = [
    "wasm32-unknown-unknown",
    "wasm32-wasi",
    "x86_64-unknown-linux-gnu",
    "i586-pc-windows-msvc",
]

maybe?

This would not be a guarantee that these targets actually work, but cargo could issue warnings/errors to developers and the documentation can show that this crate is supposed to compile on these targets.

I issued an issue in the cargo repository: https://github.com/rust-lang/cargo/issues/8645

1 Like

it might even be useful to declare something like non-targets, to explicitly exclude targets for a crate.

I don't think it's appropriate to do this on a per-target basis. Except for crates using FFI, crates should be using standard Rust primitives so that they can be built on any target. What would be a good idea, I think, is a clearer distinction std vs no_std crates. For further subdivision beyond that, why reinvent the wheel? There's already an accepted (but unimplemented) RFC that's supposed to cover this.

The RFC seems to be about something else, about making code annotated with cfg only callable from code compatible with that cfg, no matter what, which is not the case currently.

I am not sure, but maybe the distinction std/no_std isn't sufficient for all cases. Many crates would end up in no_std, while having different compatibilities.

My specific problem is that I want to compile a crate to web assembly, i.e. something like wasm32-unknown-unknown or wasm32-wasi, but the crate has dependencies which are not compatible with those targets. The goal now is to make those dependencies compatible, but it is very laborious to find all crates which cause the incompatibility, because I need to manually compile each crate to see if it compiles on that target. It would be helpful if I could immediately see which targets a crate was explicitly (not) designed for, so that cargo could maybe even list all of the crates which are incompatible with the target you are trying to compile to. An output like this would be useful:

The following dependencies are not compatible with target 'wasm32-unknown-unknown':
├── reqwest v0.10.7
│   ├── hyper v0.13.7
│   │   ├── h2 v0.2.6
│   │   │   ├── tokio v0.2.22
│   │   │   │   ├── mio v0.6.22
│   │   │   │   │   ├── net2 v0.2.34
│   │   │   │   │   │   └── libc v0.2.76
│   ├── hyper-tls v0.4.3
│   │   ├── native-tls v0.2.4
│   │   │   ├── openssl v0.10.30
│   │   │   │   ├── libc v0.2.76
│   │   │   │   └── openssl-sys v0.9.58
│   │   │   │       └── libc v0.2.76

where the leaf entries of the tree are the crates causing the incompatibility, in this case only libc. The dependent crates automatically lose compatibility, which would mean that something like non-targets should be inherited if a dependency is used (e.g. not optional or enabled by a feature).

This would make it possible to immediately see that libc is not meant to run on web assembly, and that you would need to find an alternative for the crates that depends on it. Right now you need to look at the compiler errors, fix them for one specific crate, try again and repeat for the next incompatible crate.

1 Like

As Rust grows in popularity, new targets will continue to be added. Thus a negative set (i.e., of incompatible targets) will never be reliable. For me it would make more sense to have a positive set of targets that have been tested against the current code version. Any other target(s) would require the person/organization using the crate to verify suitability.

3 Likes

Good point. The question is how the compatible targets are decided and added to the list. I could imagine cargo making suggestions after a successful build to a target, like:

Build for target 'wasm32-unknown-unknown' has been successful, but it is not listed in 'build.targets'.

then crate authors manually add them to the list. There could also be a cargo build option which automatically adds successful targets to the list.

Because a successful build does not necessarily mean the target is supported, you would eventually have to run run-time tests to validate a target properly. Maybe it is possible to build standard test libraries to verify that a target works in a specific target environment? These could then be used to automatically validate supported targets.

Some crates do use FFI, or target-specific features, or assembly, or other things that make them non-portable. It would be helpful if such crates can provide declarative metadata indicating the targets they support.

1 Like

Also, ideally this would interact with configuration, as some feature flags (presence or absence) may not be supported on all targets.

There's also an interesting question of where this metadata should live. While it would naturally live in the Cargo.toml, it would also probably be useful to be able to retrospectively note "all versions of this crate have been compatible with wasm". Otherwise you end up both needing to publish new versions just to update metadata (which feels like overkill) and artificially restricting the version solver more than is needed.

That info should eventually be released with each documentation. Each documentation would note the compatibility of its crate version, and since the documentation history is already kept by docs.rs, tracing the compatibility history would be given, if the compatibility info resides in the Cargo.toml and is compiled into the documentation. I think it is a reasonable place to put it, because it is very much in the responsibility of the author to declare supported targets, and it interacts closely with the dependencies of a crate. Any assertions on the compatibility of a crate should go in the crate manifest in my opinion.

I agree that this is a good ideal to strive for. But I think that there's a difference between labelling something as happening to be compatible, and promising that it will be compatible. I think that Cargo.toml is a great place for a crate author to state "I intend for this crate to be compatible with a particular target, and will ensure it continues to be in the future".

But realistically, a lot of crates probably happen to be wasm-compatible, or Windows-compatible, or whatever else, without the author really thinking about it. A crate author may accept a pull request marking a crate as wasm-compatible, or may choose to reject it (because they don't intend to keep it so in the future). But it's unrealistic for a crate author to state every target their crate may be compatible with up-front.

Two particular cases where this is hard are when new targets are introduced, and in fact with the introduction of this metadata in the first place. Right now, no targets are marked as wasm-compatible (because the feature doesn't exist to record this information), and ideally if we started displaying compatibility in UIs, we would have a way to claim that currently published crates are compatible with particular targets.

So I feel like, while having space for this in Cargo.toml would be desirable, it would also be useful to be able to tag crates as compatible with specific targets outside of Cargo.toml.

Cargo will at least be able to verify that a crate compiles for a specific target. Cargo could even check for compatible targets, i.e. try to compile the crate for every target. There may still be problems with a target at runtime, but verifying if a target can be compiled against would be a first step. Cargo could then generate information about each target and store it somewhere in the crate. This information can be used by cargo doc and also by depending crates to verify their compatibility with targets. Maybe this kind of check should be part of cargo check. If cargo check is run, it will check targets for compatibility and use compatibility information from dependency crates.

Running this check would be optional, because it is not 100% safe anyways. There may be ways to run tests but those tests can't guarantee compatibility either, unless they are some kind of verified test libraries specifically designed to test compatibility for a target.

However it is already a benefit to have cargo automatically index target build compatibility to directly be able to trace build incompatibilities.

I am thinking about making this a cargo plugin for now, and see how it develops.

1 Like