Skip to content
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

feat(build): add first-class support for binary crates #736

Open
wants to merge 5 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 50 additions & 37 deletions src/bindgen.rs
Expand Up @@ -28,49 +28,62 @@ 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 = bindgen.binary("wasm-bindgen")?;
for target_name in data.targets() {
boringcactus marked this conversation as resolved.
Show resolved Hide resolved
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")
};

let mut cmd = Command::new(&bindgen_path);
cmd.arg(&wasm_path)
.arg("--out-dir")
.arg(out_dir)
.arg(dts_arg);
if !wasm_path.exists() {
continue;
}

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);
}
let dts_arg = if disable_dts {
"--no-typescript"
} else {
"--typescript"
};
let bindgen_path = bindgen.binary("wasm-bindgen")?;

if let Some(value) = out_name {
cmd.arg("--out-name").arg(value);
}
let mut cmd = Command::new(&bindgen_path);
cmd.arg(&wasm_path)
.arg("--out-dir")
.arg(out_dir)
.arg(dts_arg);

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 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
6 changes: 5 additions & 1 deletion src/build/mod.rs
Expand Up @@ -76,12 +76,13 @@ 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");
match profile {
BuildProfile::Profiling => {
// Once there are DWARF debug info consumers, force enable debug
Expand All @@ -100,6 +101,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
Expand Up @@ -34,6 +34,7 @@ pub struct Build {
pub out_name: Option<String>,
pub bindgen: Option<Download>,
pub cache: Cache,
pub example: Option<String>,
pub extra_options: Vec<String>,
}

Expand Down Expand Up @@ -148,6 +149,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(last = true)]
/// List of extra options to pass to `cargo build`
pub extra_options: Vec<String>,
Expand All @@ -167,6 +172,7 @@ impl Default for BuildOptions {
profiling: false,
out_dir: String::new(),
out_name: None,
example: None,
extra_options: Vec::new(),
}
}
Expand All @@ -178,7 +184,11 @@ impl Build {
/// Construct a build command from the given options.
pub fn try_from_opts(build_opts: BuildOptions) -> Result<Self, Error> {
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 @@ -203,6 +213,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 @@ -298,7 +309,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
Expand Up @@ -119,7 +119,7 @@ impl Test {
} = test_opts;

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
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 @@ -402,7 +403,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 @@ -431,6 +436,7 @@ impl CrateData {
manifest,
current_idx,
out_name,
example,
})
}

Expand Down Expand Up @@ -493,18 +499,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 @@ -524,6 +542,22 @@ impl CrateData {
}
}

/// Get the target names that will be built for the current crate.
pub fn targets(&self) -> impl Iterator<Item = String> + '_ {
boringcactus marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Up @@ -295,3 +295,62 @@ fn build_force() {
.assert()
.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());
}
10 changes: 5 additions & 5 deletions tests/all/license.rs
Expand Up @@ -12,7 +12,7 @@ fn it_copies_a_license_default_path() {
let fixture = fixture::single_license();
let out_dir = fixture.path.join("pkg");
fs::create_dir(&out_dir).expect("should create pkg directory OK");
let crate_data = CrateData::new(&fixture.path, None);
let crate_data = CrateData::new(&fixture.path, None, None);

assert!(license::copy_from_crate(&crate_data.unwrap(), &fixture.path, &out_dir).is_ok());

Expand All @@ -37,7 +37,7 @@ fn it_copies_a_license_provided_path() {
let fixture = fixture::single_license();
let out_dir = fixture.path.join("pkg");
fs::create_dir(&out_dir).expect("should create pkg directory OK");
let crate_data = CrateData::new(&fixture.path, None);
let crate_data = CrateData::new(&fixture.path, None, None);

assert!(license::copy_from_crate(&crate_data.unwrap(), &fixture.path, &out_dir).is_ok());
let crate_license_path = fixture.path.join("LICENSE");
Expand All @@ -60,7 +60,7 @@ fn it_copies_all_licenses_default_path() {
let fixture = fixture::dual_license();
let out_dir = fixture.path.join("pkg");
fs::create_dir(&out_dir).expect("should create pkg directory OK");
let crate_data = CrateData::new(&fixture.path, None);
let crate_data = CrateData::new(&fixture.path, None, None);

assert!(license::copy_from_crate(&crate_data.unwrap(), &fixture.path, &out_dir).is_ok());

Expand Down Expand Up @@ -95,7 +95,7 @@ fn it_copies_all_licenses_provided_path() {
let fixture = fixture::dual_license();
let out_dir = fixture.path.join("pkg");
fs::create_dir(&out_dir).expect("should create pkg directory OK");
let crate_data = CrateData::new(&fixture.path, None);
let crate_data = CrateData::new(&fixture.path, None, None);

assert!(license::copy_from_crate(&crate_data.unwrap(), &fixture.path, &out_dir).is_ok());

Expand Down Expand Up @@ -131,7 +131,7 @@ fn it_copies_a_non_standard_license_provided_path() {
let fixture = fixture::non_standard_license(license_file);
let out_dir = fixture.path.join("pkg");
fs::create_dir(&out_dir).expect("should create pkg directory OK");
let crate_data = CrateData::new(&fixture.path, None);
let crate_data = CrateData::new(&fixture.path, None, None);

assert!(license::copy_from_crate(&crate_data.unwrap(), &fixture.path, &out_dir).is_ok());

Expand Down