-
Notifications
You must be signed in to change notification settings - Fork 29
Automatically configure getrandom web backend #547
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
69cb733
b847a42
ec50459
ddc0c2f
16b7cb2
3b8d1ca
993dfae
dddda40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,52 @@ | ||||||
# `getrandom` | ||||||
|
||||||
`getrandom` is a popular crate that allows retrieving random data from system resources. | ||||||
It is used by `bevy` and many other crates. | ||||||
It provides multiple backends which retrieve the random data from different sources. | ||||||
|
||||||
This works great, except when targeting the web. | ||||||
Unlike most other platforms, there is no compilation target available that guarantees web APIs to exist. | ||||||
Just because you're building for `wasm32-unknown-unknown` doesn't necessarily mean you are making a web app -- | ||||||
you could also be building a standalone Wasm application. | ||||||
|
||||||
So the target cannot be used to automatically activate the web backend. | ||||||
Features are also inadequate: They are additive, so if _any_ dependency would pull in the web feature, | ||||||
the backend would be used everywhere. | ||||||
TimJentzsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
Considering the security-sensitive nature of random data, this was deemed unacceptable. | ||||||
|
||||||
So in addition to a feature to make the backend available, | ||||||
`getrandom` requires you to pass a `RUSTFLAG` to the compiler to activate the backend. | ||||||
This guarantees that the backend can only be configured once (by "outer" package). | ||||||
|
||||||
## Configuring the web backend | ||||||
|
||||||
Since you are the application developer, you _know_ that you are building for the web and not just any Wasm target. | ||||||
This allows you to set up the `getrandom` backend properly, for example like this: | ||||||
|
||||||
```toml | ||||||
[target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies] | ||||||
getrandom = { version = "0.3", features = ["wasm_js"] } | ||||||
getrandom_02 = { version = "0.2", features = ["js"], package = "getrandom" } | ||||||
``` | ||||||
|
||||||
This activates the necessary feature flags for `getrandom`, accounting for both v0.2 and v0.3 (as they can both be in the dependency tree). | ||||||
TimJentzsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
Additionally, you need to add `--cfg getrandom_backend="wasm_js"` to your `RUSTFLAGS`. | ||||||
This can be done in several places: | ||||||
|
||||||
- Setting `rustflags` in `.cargo/config.toml` | ||||||
- Setting `build.rustflags` in `Cargo.toml` | ||||||
- Setting the `RUSTFLAGS` env variable when running `cargo` | ||||||
|
||||||
However, not that the rustflags are not merged, but _overwritten_. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
So if you have e.g. `rustflags` defined in your `~/.cargo/config.toml` to optimize your compile times, | ||||||
but then have again `rustflags` defined in your workspace to set the `getrandom` backend, | ||||||
only the workspace `rustflags` will be used. | ||||||
|
||||||
## Automated by the CLI | ||||||
|
||||||
The Bevy CLI automatically applies the web `getrandom` backend when `bevy build web` or `bevy run web` is used | ||||||
and you haven't configured the backend yourself. | ||||||
This allows more projects to work out-of-the-box for the web and simplifies the configuration. | ||||||
|
||||||
You can still explicitly define the backend if you wish (or need to). |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ use crate::{ | |
external_cli::{cargo, wasm_bindgen, wasm_opt}, | ||
web::{ | ||
bundle::{PackedBundle, create_web_bundle}, | ||
getrandom::apply_getrandom_backend, | ||
profiles::configure_default_web_profiles, | ||
}, | ||
}; | ||
|
@@ -27,17 +28,19 @@ pub fn build_web( | |
metadata: &Metadata, | ||
bin_target: &BinTarget, | ||
) -> anyhow::Result<WebBundle> { | ||
let web_args = args | ||
.subcommand | ||
.as_ref() | ||
.map(|BuildSubcommands::Web(web_args)| web_args); | ||
|
||
let mut profile_args = configure_default_web_profiles(metadata)?; | ||
// `--config` args are resolved from left to right, | ||
// so the default configuration needs to come before the user args | ||
profile_args.append(&mut args.cargo_args.common_args.config); | ||
args.cargo_args.common_args.config = profile_args; | ||
|
||
// Apply the `getrandom` web backend if necessary | ||
if let Some(target) = args.target() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to check if the target specified is actually There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are also other potential web targets like |
||
&& apply_getrandom_backend(args, &target).context("failed to apply getrandom backend")? | ||
{ | ||
info!("automatically configuring `getrandom` web backend"); | ||
} | ||
|
||
let cargo_args = args.cargo_args_builder(); | ||
|
||
info!("compiling to WebAssembly..."); | ||
|
@@ -53,6 +56,11 @@ pub fn build_web( | |
wasm_bindgen::bundle(metadata, bin_target, args.auto_install())?; | ||
wasm_opt::optimize_path(bin_target, args.auto_install(), &args.wasm_opt_args())?; | ||
|
||
let web_args = args | ||
.subcommand | ||
.as_ref() | ||
.map(|BuildSubcommands::Web(web_args)| web_args); | ||
|
||
let web_bundle = create_web_bundle( | ||
metadata, | ||
args.profile(), | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,113 @@ | ||||||
use semver::{Version, VersionReq}; | ||||||
|
||||||
use crate::{commands::build::BuildArgs, external_cli::cargo}; | ||||||
|
||||||
/// Apply the web backend to getrandom. | ||||||
/// | ||||||
/// This is a consistent pain point for users, so we try to automate it. | ||||||
/// | ||||||
/// There's two things you need to do: | ||||||
/// | ||||||
/// 1. Enabling the corresponding feature in the `getrandom` dependency | ||||||
/// 2. Setting the `--cfg getrandom_backend="wasm_js"` RUSTFLAG | ||||||
/// | ||||||
/// This function can handle both v2 and v3 dependencies. | ||||||
/// | ||||||
/// If `getrandom` is not a dependency, nothing happens. | ||||||
pub fn apply_getrandom_backend(args: &mut BuildArgs, target: &str) -> anyhow::Result<bool> { | ||||||
// Obtaining the metadata again to filter dependencies and their features by the used target | ||||||
let metadata = cargo::metadata::metadata_with_args(["--filter-platform", target])?; | ||||||
|
||||||
let getrandom_packages = metadata | ||||||
.packages | ||||||
.iter() | ||||||
.filter(|pkg| pkg.name.as_str() == "getrandom") | ||||||
.collect::<Vec<_>>(); | ||||||
|
||||||
if getrandom_packages.is_empty() { | ||||||
// Nothing to do when `getrandom` isn't used | ||||||
return Ok(false); | ||||||
} | ||||||
|
||||||
let mut backend_applied = false; | ||||||
|
||||||
// The package allows us to find the correct version of the dependency | ||||||
let v2 = getrandom_packages | ||||||
.iter() | ||||||
.find(|pkg| VersionReq::parse("^0.2").unwrap().matches(&pkg.version)); | ||||||
|
||||||
// getrandom v2 needs the `js` feature enabled | ||||||
if let Some(v2) = v2 { | ||||||
// The resolved dependency includes the features that are enabled | ||||||
let dep = metadata | ||||||
.resolve | ||||||
.as_ref() | ||||||
.and_then(|resolve| resolve.nodes.iter().find(|node| node.id == v2.id)); | ||||||
|
||||||
let v2_feature = "js"; | ||||||
|
||||||
if let Some(dep) = dep | ||||||
&& !dep.features.iter().any(|feature| **feature == v2_feature) | ||||||
{ | ||||||
backend_applied = true; | ||||||
|
||||||
add_feature_config( | ||||||
&mut args.cargo_args.common_args.config, | ||||||
&v2.version, | ||||||
v2_feature, | ||||||
); | ||||||
} | ||||||
} | ||||||
|
||||||
// getrandom v3 needs the `wasm_js` feature enabled | ||||||
// and the `--cfg getrandom_backend="wasm_js"` RUSTFLAG must be set | ||||||
let v3 = getrandom_packages | ||||||
.iter() | ||||||
.find(|pkg| VersionReq::parse("^0.3").unwrap().matches(&pkg.version)); | ||||||
|
||||||
if let Some(v3) = v3 { | ||||||
let dep = metadata | ||||||
.resolve | ||||||
.as_ref() | ||||||
.and_then(|resolve| resolve.nodes.iter().find(|node| node.id == v3.id)); | ||||||
|
||||||
let v3_feature = "wasm_js"; | ||||||
|
||||||
if let Some(dep) = dep | ||||||
&& !dep.features.iter().any(|feature| **feature == v3_feature) | ||||||
{ | ||||||
backend_applied = true; | ||||||
|
||||||
add_feature_config( | ||||||
&mut args.cargo_args.common_args.config, | ||||||
&v3.version, | ||||||
v3_feature, | ||||||
); | ||||||
} | ||||||
|
||||||
let mut rustflags = args | ||||||
.cargo_args | ||||||
.common_args | ||||||
.rustflags | ||||||
.clone() | ||||||
.unwrap_or_default(); | ||||||
|
||||||
if !rustflags.contains("--cfg getrandom_backend") { | ||||||
TimJentzsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
backend_applied = true; | ||||||
rustflags += " --cfg getrandom_backend=\"wasm_js\""; | ||||||
args.cargo_args.common_args.rustflags = Some(rustflags); | ||||||
} | ||||||
} | ||||||
|
||||||
Ok(backend_applied) | ||||||
} | ||||||
|
||||||
/// Adds the feature configuration for the `getrandom` dependency | ||||||
fn add_feature_config(config: &mut Vec<String>, version: &Version, feature: &str) { | ||||||
// Distinguish entries by version to allow multiple versions to be configured | ||||||
let table = format!("dependencies.getrandom_{}{}", version.major, version.minor); | ||||||
// The config arg doesn't support inline tables, so each entries must be configured separately | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo:
Suggested change
|
||||||
config.push(format!(r#"{table}.package="getrandom""#)); | ||||||
config.push(format!(r#"{table}.version="{}""#, version)); | ||||||
config.push(format!(r#"{table}.features=["{feature}"]"#)); | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's just me as a none native speaker but at first it read for me like
"for
bevy build web
to automatically configure it, you have to enable a feature and some rustflags" until I then realize that the "It" refers togetrandom
.