Skip to content

Add first-class support for binary crates #816

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 54 additions & 40 deletions src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,50 +27,64 @@ pub fn wasm_bindgen_build(

let out_dir = out_dir.to_str().unwrap();

let wasm_path = data
.target_directory()
.join("wasm32-unknown-unknown")
.join(release_or_debug)
.join(data.crate_name())
.with_extension("wasm");

let dts_arg = if disable_dts {
"--no-typescript"
} else {
"--typescript"
};
let bindgen_path = install::get_tool_path(install_status, Tool::WasmBindgen)?
.binary(&Tool::WasmBindgen.to_string())?;

let mut cmd = Command::new(&bindgen_path);
cmd.arg(&wasm_path)
.arg("--out-dir")
.arg(out_dir)
.arg(dts_arg);

let target_arg = build_target_arg(target, &bindgen_path)?;
if supports_dash_dash_target(bindgen_path.to_path_buf())? {
cmd.arg("--target").arg(target_arg);
} else {
cmd.arg(target_arg);
}
for target_name in data.targets() {
let wasm_path = {
let wasm_path = data
.target_directory()
.join("wasm32-unknown-unknown")
.join(release_or_debug);
let wasm_path = if data.is_example() {
wasm_path.join("examples")
} else {
wasm_path
};
wasm_path.join(target_name).with_extension("wasm")
};

if let Some(value) = out_name {
cmd.arg("--out-name").arg(value);
}
if !wasm_path.exists() {
continue;
}

let profile = data.configured_profile(profile);
if profile.wasm_bindgen_debug_js_glue() {
cmd.arg("--debug");
}
if !profile.wasm_bindgen_demangle_name_section() {
cmd.arg("--no-demangle");
}
if profile.wasm_bindgen_dwarf_debug_info() {
cmd.arg("--keep-debug");
let dts_arg = if disable_dts {
"--no-typescript"
} else {
"--typescript"
};

let bindgen_path = install::get_tool_path(install_status, Tool::WasmBindgen)?
.binary(&Tool::WasmBindgen.to_string())?;

let mut cmd = Command::new(&bindgen_path);
cmd.arg(&wasm_path)
.arg("--out-dir")
.arg(out_dir)
.arg(dts_arg);

let target_arg = build_target_arg(target, &bindgen_path)?;
if supports_dash_dash_target(bindgen_path)? {
cmd.arg("--target").arg(target_arg);
} else {
cmd.arg(target_arg);
}

if let Some(value) = out_name {
cmd.arg("--out-name").arg(value);
}

let profile = data.configured_profile(profile);
if profile.wasm_bindgen_debug_js_glue() {
cmd.arg("--debug");
}
if !profile.wasm_bindgen_demangle_name_section() {
cmd.arg("--no-demangle");
}
if profile.wasm_bindgen_dwarf_debug_info() {
cmd.arg("--keep-debug");
}

child::run(cmd, "wasm-bindgen").context("Running the wasm-bindgen CLI")?;
}

child::run(cmd, "wasm-bindgen").context("Running the wasm-bindgen CLI")?;
Ok(())
}

Expand Down
7 changes: 6 additions & 1 deletion src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ fn wasm_pack_local_version() -> Option<String> {
pub fn cargo_build_wasm(
path: &Path,
profile: BuildProfile,
example: &Option<String>,
extra_options: &[String],
) -> Result<(), Error> {
let msg = format!("{}Compiling to Wasm...", emoji::CYCLONE);
PBAR.info(&msg);

let mut cmd = Command::new("cargo");
cmd.current_dir(path).arg("build").arg("--lib");

cmd.current_dir(path).arg("build");

if PBAR.quiet() {
cmd.arg("--quiet");
Expand All @@ -107,6 +109,9 @@ pub fn cargo_build_wasm(
}

cmd.arg("--target").arg("wasm32-unknown-unknown");
if let Some(example) = example {
cmd.arg("--example").arg(example);
}
cmd.args(extra_options);
child::run(cmd, "cargo build").context("Compiling your crate to WebAssembly failed")?;
Ok(())
Expand Down
20 changes: 18 additions & 2 deletions src/command/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub struct Build {
pub out_name: Option<String>,
pub bindgen: Option<install::Status>,
pub cache: Cache,
pub example: Option<String>,
pub extra_options: Vec<String>,
}

Expand Down Expand Up @@ -156,6 +157,10 @@ pub struct BuildOptions {
/// Sets the output file names. Defaults to package name.
pub out_name: Option<String>,

#[structopt(long = "example")]
/// Builds the specified example.
pub example: Option<String>,

#[structopt(allow_hyphen_values = true)]
/// List of extra options to pass to `cargo build`
pub extra_options: Vec<String>,
Expand All @@ -175,6 +180,7 @@ impl Default for BuildOptions {
profiling: false,
out_dir: String::new(),
out_name: None,
example: None,
extra_options: Vec::new(),
}
}
Expand All @@ -194,7 +200,11 @@ impl Build {
}
}
let crate_path = get_crate_path(build_opts.path)?;
let crate_data = manifest::CrateData::new(&crate_path, build_opts.out_name.clone())?;
let crate_data = manifest::CrateData::new(
&crate_path,
build_opts.out_name.clone(),
build_opts.example.clone(),
)?;
let out_dir = crate_path.join(PathBuf::from(build_opts.out_dir));

let dev = build_opts.dev || build_opts.debug;
Expand All @@ -219,6 +229,7 @@ impl Build {
out_name: build_opts.out_name,
bindgen: None,
cache: cache::get_wasm_pack_cache()?,
example: build_opts.example,
extra_options: build_opts.extra_options,
})
}
Expand Down Expand Up @@ -314,7 +325,12 @@ impl Build {

fn step_build_wasm(&mut self) -> Result<(), Error> {
info!("Building wasm...");
build::cargo_build_wasm(&self.crate_path, self.profile, &self.extra_options)?;
build::cargo_build_wasm(
&self.crate_path,
self.profile,
&self.example,
&self.extra_options,
)?;

info!(
"wasm built at {:#?}.",
Expand Down
2 changes: 1 addition & 1 deletion src/command/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl Test {
};

let crate_path = get_crate_path(path)?;
let crate_data = manifest::CrateData::new(&crate_path, None)?;
let crate_data = manifest::CrateData::new(&crate_path, None, None)?;
let any_browser = chrome || firefox || safari;

if !node && !any_browser {
Expand Down
50 changes: 42 additions & 8 deletions src/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct CrateData {
current_idx: usize,
manifest: CargoManifest,
out_name: Option<String>,
example: Option<String>,
}

#[doc(hidden)]
Expand Down Expand Up @@ -403,7 +404,11 @@ pub struct ManifestAndUnsedKeys {
impl CrateData {
/// Reads all metadata for the crate whose manifest is inside the directory
/// specified by `path`.
pub fn new(crate_path: &Path, out_name: Option<String>) -> Result<CrateData, Error> {
pub fn new(
crate_path: &Path,
out_name: Option<String>,
example: Option<String>,
) -> Result<CrateData, Error> {
let manifest_path = crate_path.join("Cargo.toml");
if !manifest_path.is_file() {
bail!(
Expand Down Expand Up @@ -435,6 +440,7 @@ impl CrateData {
manifest,
current_idx,
out_name,
example,
})
}

Expand Down Expand Up @@ -506,18 +512,30 @@ impl CrateData {
Ok(())
}

fn check_crate_type(&self) -> Result<(), Error> {
fn valid_targets(&self) -> impl Iterator<Item = &cargo_metadata::Target> {
fn valid(target: &cargo_metadata::Target, example: &Option<String>) -> bool {
if let Some(example) = example {
target.name == *example && target.kind.iter().any(|k| k == "example")
} else {
fn valid_kind(x: &str) -> bool {
x == "cdylib" || x == "bin"
}
target.kind.iter().any(|x| valid_kind(x))
}
}
let pkg = &self.data.packages[self.current_idx];
let any_cdylib = pkg
.targets
pkg.targets
.iter()
.filter(|target| target.kind.iter().any(|k| k == "cdylib"))
.any(|target| target.crate_types.iter().any(|s| s == "cdylib"));
if any_cdylib {
.filter(move |target| valid(target, &self.example))
}

fn check_crate_type(&self) -> Result<(), Error> {
let any_valid = self.valid_targets().count() > 0;
if any_valid {
return Ok(());
}
bail!(
"crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your \
"library crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your \
Cargo.toml file:\n\n\
[lib]\n\
crate-type = [\"cdylib\", \"rlib\"]"
Expand All @@ -537,6 +555,22 @@ impl CrateData {
}
}

/// Get the target names that will be built for the current crate.
pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
self.valid_targets().map(|x| {
if x.kind.iter().any(|x| x.ends_with("lib")) {
x.name.replace("-", "_")
} else {
x.name.clone()
}
})
}

/// Check if we are building an example.
pub fn is_example(&self) -> bool {
self.example.is_some()
}

/// Get the prefix for output file names
pub fn name_prefix(&self) -> String {
match &self.out_name {
Expand Down
59 changes: 59 additions & 0 deletions tests/all/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,65 @@ fn build_force() {
.success();
}

#[test]
fn bin_crate_behavior_identical() {
let fixture = utils::fixture::bin_crate();
fixture.install_local_wasm_bindgen();
fixture
.wasm_pack()
.arg("build")
.arg("--target")
.arg("nodejs")
.assert()
.success();
let native_output = fixture.command("cargo").arg("run").output().unwrap();
assert!(native_output.status.success());
assert_eq!(native_output.stdout, b"Hello, World\n");
let wasm_output = fixture.command("node").arg("pkg/foo.js").output().unwrap();
assert!(wasm_output.status.success());
assert_eq!(wasm_output.stdout, b"Hello, World\n");
}

#[test]
fn multi_bin_crate_procs_all() {
let fixture = utils::fixture::multi_bin_crate();
fixture.install_local_wasm_bindgen();
fixture
.wasm_pack()
.arg("build")
.arg("--target")
.arg("nodejs")
.assert()
.success();
let pkg_path = |x: &str| {
let mut path = fixture.path.clone();
path.push("pkg");
path.push(x);
path
};
assert!(pkg_path("foo.js").exists());
assert!(pkg_path("sample.js").exists());
}

#[test]
fn builds_examples() {
let fixture = utils::fixture::bin_example_crate();
fixture.install_local_wasm_bindgen();
fixture
.wasm_pack()
.arg("build")
.arg("--target")
.arg("nodejs")
.arg("--example")
.arg("example")
.assert()
.success();
let mut path = fixture.path.clone();
path.push("pkg");
path.push("example.js");
assert!(path.exists());
}

#[test]
fn build_from_new() {
let fixture = utils::fixture::not_a_crate();
Expand Down
Loading