wasm_builder/
builder.rs

1// This file is part of Tetcore.
2
3// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use std::{env, path::{PathBuf, Path}, process};
19
20/// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env.
21fn get_manifest_dir() -> PathBuf {
22	env::var("CARGO_MANIFEST_DIR")
23		.expect("`CARGO_MANIFEST_DIR` is always set for `build.rs` files; qed")
24		.into()
25}
26
27/// First step of the [`WasmBuilder`] to select the project to build.
28pub struct WasmBuilderSelectProject {
29	/// This parameter just exists to make it impossible to construct
30	/// this type outside of this crate.
31	_ignore: (),
32}
33
34impl WasmBuilderSelectProject {
35	/// Use the current project as project for building the WASM binary.
36	///
37	/// # Panics
38	///
39	/// Panics if the `CARGO_MANIFEST_DIR` variable is not set. This variable
40	/// is always set by `Cargo` in `build.rs` files.
41	pub fn with_current_project(self) -> WasmBuilder {
42		WasmBuilder {
43			rust_flags: Vec::new(),
44			file_name: None,
45			project_cargo_toml: get_manifest_dir().join("Cargo.toml"),
46		}
47	}
48
49	/// Use the given `path` as project for building the WASM binary.
50	///
51	/// Returns an error if the given `path` does not points to a `Cargo.toml`.
52	pub fn with_project(
53		self,
54		path: impl Into<PathBuf>,
55	) -> Result<WasmBuilder, &'static str> {
56		let path = path.into();
57
58		if path.ends_with("Cargo.toml") && path.exists() {
59			Ok(WasmBuilder {
60				rust_flags: Vec::new(),
61				file_name: None,
62				project_cargo_toml: path,
63			})
64		} else {
65			Err("Project path must point to the `Cargo.toml` of the project")
66		}
67	}
68}
69
70/// The builder for building a wasm binary.
71///
72/// The builder itself is separated into multiple structs to make the setup type safe.
73///
74/// Building a wasm binary:
75///
76/// 1. Call [`WasmBuilder::new`] to create a new builder.
77/// 2. Select the project to build using the methods of [`WasmBuilderSelectProject`].
78/// 3. Set additional `RUST_FLAGS` or a different name for the file containing the WASM code
79///    using methods of [`WasmBuilder`].
80/// 4. Build the WASM binary using [`Self::build`].
81pub struct WasmBuilder {
82	/// Flags that should be appended to `RUST_FLAGS` env variable.
83	rust_flags: Vec<String>,
84	/// The name of the file that is being generated in `OUT_DIR`.
85	///
86	/// Defaults to `wasm_binary.rs`.
87	file_name: Option<String>,
88	/// The path to the `Cargo.toml` of the project that should be built
89	/// for wasm.
90	project_cargo_toml: PathBuf,
91}
92
93impl WasmBuilder {
94	/// Create a new instance of the builder.
95	pub fn new() -> WasmBuilderSelectProject {
96		WasmBuilderSelectProject {
97			_ignore: (),
98		}
99	}
100
101	/// Enable exporting `__heap_base` as global variable in the WASM binary.
102	///
103	/// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`.
104	pub fn export_heap_base(mut self) -> Self {
105		self.rust_flags.push("-Clink-arg=--export=__heap_base".into());
106		self
107	}
108
109	/// Set the name of the file that will be generated in `OUT_DIR`.
110	///
111	/// This file needs to be included to get access to the build WASM binary.
112	///
113	/// If this function is not called, `file_name` defaults to `wasm_binary.rs`
114	pub fn set_file_name(mut self, file_name: impl Into<String>) -> Self {
115		self.file_name = Some(file_name.into());
116		self
117	}
118
119	/// Instruct the linker to import the memory into the WASM binary.
120	///
121	/// This adds `-C link-arg=--import-memory` to `RUST_FLAGS`.
122	pub fn import_memory(mut self) -> Self {
123		self.rust_flags.push("-C link-arg=--import-memory".into());
124		self
125	}
126
127	/// Append the given `flag` to `RUST_FLAGS`.
128	///
129	/// `flag` is appended as is, so it needs to be a valid flag.
130	pub fn append_to_rust_flags(mut self, flag: impl Into<String>) -> Self {
131		self.rust_flags.push(flag.into());
132		self
133	}
134
135	/// Build the WASM binary.
136	pub fn build(self) {
137		let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!"));
138		let file_path = out_dir.join(self.file_name.unwrap_or_else(|| "wasm_binary.rs".into()));
139
140		if check_skip_build() {
141			// If we skip the build, we still want to make sure to be called when an env variable
142			// changes
143			generate_rerun_if_changed_instructions();
144
145			provide_dummy_wasm_binary_if_not_exist(&file_path);
146
147			return;
148		}
149
150		build_project(
151			file_path,
152			self.project_cargo_toml,
153			self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect(),
154		);
155
156		// As last step we need to generate our `rerun-if-changed` stuff. If a build fails, we don't
157		// want to spam the output!
158		generate_rerun_if_changed_instructions();
159	}
160}
161
162/// Generate the name of the skip build environment variable for the current crate.
163fn generate_crate_skip_build_env_name() -> String {
164	format!(
165		"SKIP_{}_WASM_BUILD",
166		env::var("CARGO_PKG_NAME").expect("Package name is set").to_uppercase().replace('-', "_"),
167	)
168}
169
170/// Checks if the build of the WASM binary should be skipped.
171fn check_skip_build() -> bool {
172	env::var(crate::SKIP_BUILD_ENV).is_ok() || env::var(generate_crate_skip_build_env_name()).is_ok()
173}
174
175/// Provide a dummy WASM binary if there doesn't exist one.
176fn provide_dummy_wasm_binary_if_not_exist(file_path: &Path) {
177	if !file_path.exists() {
178		crate::write_file_if_changed(
179			file_path,
180			"pub const WASM_BINARY: Option<&[u8]> = None;\
181			 pub const WASM_BINARY_BLOATY: Option<&[u8]> = None;",
182		);
183	}
184}
185
186/// Generate the `rerun-if-changed` instructions for cargo to make sure that the WASM binary is
187/// rebuilt when needed.
188fn generate_rerun_if_changed_instructions() {
189	// Make sure that the `build.rs` is called again if one of the following env variables changes.
190	println!("cargo:rerun-if-env-changed={}", crate::SKIP_BUILD_ENV);
191	println!("cargo:rerun-if-env-changed={}", crate::FORCE_WASM_BUILD_ENV);
192	println!("cargo:rerun-if-env-changed={}", generate_crate_skip_build_env_name());
193}
194
195/// Build the currently built project as wasm binary.
196///
197/// The current project is determined by using the `CARGO_MANIFEST_DIR` environment variable.
198///
199/// `file_name` - The name + path of the file being generated. The file contains the
200///               constant `WASM_BINARY`, which contains the built WASM binary.
201/// `project_cargo_toml` - The path to the `Cargo.toml` of the project that should be built.
202/// `default_rustflags` - Default `RUSTFLAGS` that will always be set for the build.
203fn build_project(
204	file_name: PathBuf,
205	project_cargo_toml: PathBuf,
206	default_rustflags: String,
207) {
208	let cargo_cmd = match crate::prerequisites::check() {
209		Ok(cmd) => cmd,
210		Err(err_msg) => {
211			eprintln!("{}", err_msg);
212			process::exit(1);
213		},
214	};
215
216	let (wasm_binary, bloaty) = crate::wasm_project::create_and_compile(
217		&project_cargo_toml,
218		&default_rustflags,
219		cargo_cmd,
220	);
221
222	let (wasm_binary, wasm_binary_bloaty) = if let Some(wasm_binary) = wasm_binary {
223		(
224			wasm_binary.wasm_binary_path_escaped(),
225			bloaty.wasm_binary_bloaty_path_escaped(),
226		)
227	} else {
228		(
229			bloaty.wasm_binary_bloaty_path_escaped(),
230			bloaty.wasm_binary_bloaty_path_escaped(),
231		)
232	};
233
234	crate::write_file_if_changed(
235		file_name,
236		format!(
237			r#"
238				pub const WASM_BINARY: Option<&[u8]> = Some(include_bytes!("{wasm_binary}"));
239				pub const WASM_BINARY_BLOATY: Option<&[u8]> = Some(include_bytes!("{wasm_binary_bloaty}"));
240			"#,
241			wasm_binary = wasm_binary,
242			wasm_binary_bloaty = wasm_binary_bloaty,
243		),
244	);
245}