First off: thanks for a nice addition to the Rust ecosystem, it's really wonderful to see Microsoft actively try to support what is going on and provide support for the win32 apis.
However as a maintainer of several code-generation crates I've found that it's usually significantly nicer for the user of my crates to provide a pre-built version of the wrappers that I'm exposing. This has a few benefits
build.rs is a serial dependency and very slow for more complex projects since cargo has to stall building code further down the line, and it has to wait for code-gen crates like syn and quote to have been built before it can run a build.rs.cargo deny for example.To see the impact of building this crate, one can run cargo +nightly build -Ztimings to see the build dependency graph and this crate's impact on it (for example on the minesweeper-rs example that you've provided it looks like the vast majority of it's ~70s build time is spent in code-gen related things).
If a pre-generated version of this crate becomes available (or nicers: this crate switches into a pre-built library) please add feature toggles similar to how winapi-rs does things, this lets the cargo feature resolver make sure that the minimal set of features is built across the entire dependency tree.
I'm personally in favor of that. The API surface is huge though so it's not exactly clear how best one would offer a pre-built version. So, some additional questions:
I don't think it is terribly wrong to give an ability to pre-generate and include in crates just the bindings they need. As you mention it can lead to incompatibilities if these are exposed in the public interface of the crates, but in my experience the need to do so is pretty rare.
In my experience the winapi 0.3 approach is proven to work well, single huge crate but with features for all areas/modules that are disabled by default so each crate using it has to opt in to the feature they do want to use. This compiles nicely and the cargo additive feature support makes sure winapi is built once with all the features that all dependencies have enabled.
In our codebase we have ~50 direct usages from our own and third party crates to winapi (0.3) and it builds really fast and is easy to use. Think almost none of those crates expose the winapi types as public symbols, but some may/could and it would still work well also.
In my experience the winapi 0.3 approach is proven to work well, single huge crate but with features for all areas/modules that are disabled by default so each crate using it has to opt in to the feature they do want to use. This compiles nicely and the cargo additive feature support makes sure winapi is built once with all the features that all dependencies have enabled.
IME it hurts in large workspaces, since cargo test -p <onecrate> and cargo test -p <anothercrate> will end up with different feature combos for winapi, which will result in rebuilding it and all dependencies (which in practice is a lot of your workspace dep tree) each time. You see things like https://searchfox.org/mozilla-central/source/build/workspace-hack/Cargo.toml sometimes that exist to mitigate it (which is only realistically viable if you're not on crates.io, so is not that common).
Personally I'd prefer to use it in a pre-generation context, which avoids all these issues.
IME it hurts in large workspaces, since
cargo test -p <onecrate>andcargo test -p <anothercrate>will end up with different feature combos for winapi, which will result in rebuilding it and all dependencies
Yup that is true and a fair point, we do have a fairly large workspace (~550 crate dependencies) but less common to build and test subparts of it. But definitely does happen and is a common cargo workflow in general.
What should probably happen is a generator / default-generated split.
The generator at work here is amazing work, and it's fine for it to just be a thing that exists on its own.
However, many people want to use a pre-generated form, chunked up by feature flags or whatever. Just offer that as a crate separate from the generator itself.
Hek, just run this generator and then use the output as the content of winapi (in a 0.4 version)
less common to build and test subparts of it.
Yeah, there's a size of workspace where this becomes untenable.
IME it hurts in large workspaces, since cargo test -p
and cargo test -p will end up with different feature combos for winapi, which will result in rebuilding it and all dependencies (which in practice is a lot of your workspace dep tree) each time.
This feels like something that could be fixed in cargo itself, e.g. through the deduplicate workspace information RFC eventually. Through this RFC, you could specify the features once in the workspace root, and all the packages in your workspace would end up using a common build.
Personally, I can't use this crate without a pregenerated version being made available. Having every single crate generate their own bindings through windows-rs feels like a recipe for disaster in terms of compile-time regressions compared to the current winapi-rs approach.
This feels like something that could be fixed in cargo itself, e.g. through the deduplicate workspace information RFC eventually
It feels to me like without a major redesign, it will always be something of an issue, but perhaps I'm too pessimistic.
Having every single crate generate their own bindings through windows-rs feels like a recipe for disaster in terms of compile-time regressions compared to the current winapi-rs approach.
Yes, I agree here, unless the pregeneration approach is widely taken, which it probably won't be, since it will always be a bit too work intensive. That said, I'd really like it as an option (Plenty of crates already do this by hand, it would be valuable to improve things for them).
Sorry for the delay in responding. This is an important issue to us. In fact, we had a meeting to discuss this topic just yesterday. This matters for some of our internal teams as well. We would like to provide pre-generated bindings as a collection of crates, but there are pros and cons to doing so today. Here are a few issues to consider:
windows crate can generate far exceed those of the hand-written winapi crate. Since we tap into metadata, we can generate bindings for the entire set of Win32, COM, and WinRT APIs. We thus have scalability issues that are not as obvious in a hand-written approach.So there are just a few challenges and ideas we're throwing around. The main benefit we see for pre-generated bindings is to allow crates to share common definitions of Windows types. This is something we would like to support, but there are technical hurdles to overcome. These are hard problems, and we look forward to working together to solve them.
I've been thinking more about this. I'd like to try to capture the pros and cons of pre-built crates as well as the dependencies. Some of this will be a repeat of what @kennykerr says above but hopefully this provides a bit more structure to think about this problem.
Debug implementations should be behind a feature flag. I definitely think no matter what happens we will keep on-demand generation as an option. It's extremely valuable in many situations. However, a pre-built crate may be an additional choice for those who want/need it.
Potentially, we might be able to let these different approaches work well together. For instance, we could provide a Win32 crate which only contains definitions in the Win32 namespace. Then when the user is generating bindings outside this namespace, they could optionally let the code generator know that they want to depend on the Win32 definitions in the win32 crate instead of generating those definitions. There are issues with version management, but this is certainly a possibility.
After some testing, I found that the Win32 namespace has dependencies only on two other namespaces: Foundation and System. Luckily these namespaces do not depend on any others. So if we go down the route of a Win32 crate, we'll need to decide on some things:
Possibly silly idea: could the generator (since it has the full metadata) solve dependency cycles and generate a windows_core module to break them up? This would allow the namespaces to be exposed as individual crates.
@Plecra this is something that we could explore, but it seems that namespaces often have circular dependencies which would not be served well by a separate crate model. Also, even if this is possible now, we would essentially be committing to this model forever. What happens when new APIs come along that break this model? The Windows APIs have a lot bigger concerns than if the break the Rust bindings, so it's unlikely we would be able to prevent such a thing from happening.
Like I said above, perhaps we might be able to build a model that handles both pre-generated and non-pre-generated bindings. Then for some select namespaces, we can pre-generate the bindings if we feel comfortable enough that those namespaces work well in such a model.
Can you point out some of the circular dependencies that cause trouble? Because I think that enough of Win32 is non-circular that it's not a big deal if a few fringe cases have to be combined into a single loop of stuff that you add all at once.
@Lokathor I don't have an example off the top of my head. I will try to do some spelunking and find some. For win32 specifically (i.e., the things in the metadata under the win32 namespace), it doesn't seem like there are any dependencies currently outside of Foundation and System namespaces. So hopefully we can provide a win32 crate. The next step is to create such a crate and see how bad compile times are for each sub-namespace of the win32 namespace.
It would be insightful to use the metadata reader to model the dependencies. I would however be quite reluctant to favor some APIs over others. Ideally, we can find a solution that works for all APIs and not just some blessed APIs that happen to not have cyclic issues.
Yeah. Not to try and diminish the work on the other APIs also being supported, but I think at this time that most people are only using Win32, particularly because that's where the winapi support is at the moment.
My suggestion would be to coordinate with @retep998 about possibly having the next semver break of winapi (0.4) be generated via this generation system, and having it focus on just the win32 part of things. I think this would produce the least overall churn in the ecosystem.
Here's a quick little dump of dependencies.
use std::collections::{BTreeMap, BTreeSet};
fn main() {
let reader = gen::TypeReader::get();
let mut deps = BTreeMap::<&'static str, BTreeSet<&'static str>>::new();
for ns in reader.namespaces() {
for et in reader.namespace_types(ns) {
for def in et.dependencies() {
if et.namespace() != def.namespace() && !def.namespace().is_empty() {
deps.entry(et.namespace())
.or_default()
.insert(def.namespace());
}
}
}
}
println!("{:#?}", deps);
}
Output: https://gist.github.com/kennykerr/b27b6e803078336c2d55780109820c3e
I did some analysis on the dependency output that Kenny posted.
Windows.Foundation.Windows.Foundation and Windows.System. It is the only namespace dependent on Windows.System.Windows.Win32We need the following two features to be able to experiment with this:
build! macroI see that there are three strategies:
Notice the case 2. If your library is merely using the bindings, it's wasteful to depend on macro generation because every user of the library would generate the same bindings. Thus it's cheaper for us all if the bindings are pre-generated and bundled as code for that kind of libraries.
Most helpful comment
I've been thinking more about this. I'd like to try to capture the pros and cons of pre-built crates as well as the dependencies. Some of this will be a repeat of what @kennykerr says above but hopefully this provides a bit more structure to think about this problem.
Pros
Cons
Next steps
Debugimplementations should be behind a feature flag.I definitely think no matter what happens we will keep on-demand generation as an option. It's extremely valuable in many situations. However, a pre-built crate may be an additional choice for those who want/need it.
More ideas
Potentially, we might be able to let these different approaches work well together. For instance, we could provide a Win32 crate which only contains definitions in the Win32 namespace. Then when the user is generating bindings outside this namespace, they could optionally let the code generator know that they want to depend on the Win32 definitions in the
win32crate instead of generating those definitions. There are issues with version management, but this is certainly a possibility.After some testing, I found that the Win32 namespace has dependencies only on two other namespaces: Foundation and System. Luckily these namespaces do not depend on any others. So if we go down the route of a Win32 crate, we'll need to decide on some things: