fork of https://github.com/rustwasm/wasm-pack for the needs of NextGraph.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
9.5 KiB
327 lines
9.5 KiB
//! Reading and writing Cargo.toml and package.json manifests.
|
|
|
|
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::path::Path;
|
|
|
|
use console::style;
|
|
use emoji;
|
|
use error::Error;
|
|
use progressbar::Step;
|
|
use serde_json;
|
|
use toml;
|
|
use PBAR;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CargoManifest {
|
|
package: CargoPackage,
|
|
dependencies: Option<HashMap<String, CargoDependency>>,
|
|
#[serde(rename = "dev-dependencies")]
|
|
dev_dependencies: Option<HashMap<String, CargoDependency>>,
|
|
lib: Option<CargoLib>,
|
|
}
|
|
|
|
fn normalize_dependency_name(dep: &str) -> String {
|
|
dep.replace("-", "_")
|
|
}
|
|
|
|
fn normalize_dependencies(
|
|
deps: HashMap<String, CargoDependency>,
|
|
) -> HashMap<String, CargoDependency> {
|
|
let mut new_deps = HashMap::with_capacity(deps.len());
|
|
for (key, val) in deps {
|
|
new_deps.insert(normalize_dependency_name(&key), val);
|
|
}
|
|
new_deps
|
|
}
|
|
|
|
impl CargoManifest {
|
|
fn normalize_dependencies(&mut self) {
|
|
if let Some(deps) = self.dependencies.take() {
|
|
self.dependencies = Some(normalize_dependencies(deps));
|
|
}
|
|
if let Some(dev_deps) = self.dev_dependencies.take() {
|
|
self.dev_dependencies = Some(normalize_dependencies(dev_deps));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CargoPackage {
|
|
name: String,
|
|
authors: Vec<String>,
|
|
description: Option<String>,
|
|
version: String,
|
|
license: Option<String>,
|
|
repository: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(untagged)]
|
|
enum CargoDependency {
|
|
Simple(String),
|
|
Detailed(DetailedCargoDependency),
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct DetailedCargoDependency {
|
|
version: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CargoLib {
|
|
#[serde(rename = "crate-type")]
|
|
crate_type: Option<Vec<String>>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct NpmPackage {
|
|
name: String,
|
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
collaborators: Vec<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
description: Option<String>,
|
|
version: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
license: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
repository: Option<Repository>,
|
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
files: Vec<String>,
|
|
main: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
types: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct Repository {
|
|
#[serde(rename = "type")]
|
|
ty: String,
|
|
url: String,
|
|
}
|
|
|
|
fn read_cargo_toml(path: &Path) -> Result<CargoManifest, Error> {
|
|
let manifest_path = path.join("Cargo.toml");
|
|
if !manifest_path.is_file() {
|
|
return Error::crate_config(&format!(
|
|
"Crate directory is missing a `Cargo.toml` file; is `{}` the wrong directory?",
|
|
path.display()
|
|
)).map(|_| unreachable!());
|
|
}
|
|
let mut cargo_file = File::open(manifest_path)?;
|
|
let mut cargo_contents = String::new();
|
|
cargo_file.read_to_string(&mut cargo_contents)?;
|
|
|
|
let mut manifest: CargoManifest = toml::from_str(&cargo_contents)?;
|
|
manifest.normalize_dependencies();
|
|
|
|
Ok(manifest)
|
|
}
|
|
|
|
impl CargoManifest {
|
|
fn into_npm(mut self, scope: &Option<String>, disable_dts: bool, target: &str) -> NpmPackage {
|
|
let filename = self.package.name.replace("-", "_");
|
|
let wasm_file = format!("{}_bg.wasm", filename);
|
|
let js_file = format!("{}.js", filename);
|
|
|
|
let dts_file = if disable_dts == true {
|
|
None
|
|
} else {
|
|
Some(format!("{}.d.ts", filename))
|
|
};
|
|
|
|
let js_bg_file = if target == "nodejs" {
|
|
Some(format!("{}_bg.js", filename))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(s) = scope {
|
|
self.package.name = format!("@{}/{}", s, self.package.name);
|
|
}
|
|
let mut files = vec![wasm_file];
|
|
|
|
match dts_file {
|
|
Some(ref dts_file) => {
|
|
files.push(dts_file.to_string());
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
match js_bg_file {
|
|
Some(ref js_bg_file) => {
|
|
files.push(js_bg_file.to_string());
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
NpmPackage {
|
|
name: self.package.name,
|
|
collaborators: self.package.authors,
|
|
description: self.package.description,
|
|
version: self.package.version,
|
|
license: self.package.license,
|
|
repository: self.package.repository.map(|repo_url| Repository {
|
|
ty: "git".to_string(),
|
|
url: repo_url,
|
|
}),
|
|
files: files,
|
|
main: js_file,
|
|
types: dts_file,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Generate a package.json file inside in `./pkg`.
|
|
pub fn write_package_json(
|
|
path: &Path,
|
|
out_dir: &Path,
|
|
scope: &Option<String>,
|
|
disable_dts: bool,
|
|
target: &str,
|
|
step: &Step,
|
|
) -> Result<(), Error> {
|
|
let msg = format!("{}Writing a package.json...", emoji::MEMO);
|
|
|
|
let warn_fmt = |field| {
|
|
format!(
|
|
"Field '{}' is missing from Cargo.toml. It is not necessary, but recommended",
|
|
field
|
|
)
|
|
};
|
|
|
|
PBAR.step(step, &msg);
|
|
let pkg_file_path = out_dir.join("package.json");
|
|
let mut pkg_file = File::create(pkg_file_path)?;
|
|
let crate_data = read_cargo_toml(path)?;
|
|
let npm_data = crate_data.into_npm(scope, disable_dts, target);
|
|
|
|
if npm_data.description.is_none() {
|
|
PBAR.warn(&warn_fmt("description"));
|
|
}
|
|
if npm_data.repository.is_none() {
|
|
PBAR.warn(&warn_fmt("repository"));
|
|
}
|
|
if npm_data.license.is_none() {
|
|
PBAR.warn(&warn_fmt("license"));
|
|
}
|
|
|
|
let npm_json = serde_json::to_string_pretty(&npm_data)?;
|
|
pkg_file.write_all(npm_json.as_bytes())?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get the crate name for the crate at the given path.
|
|
pub fn get_crate_name(path: &Path) -> Result<String, Error> {
|
|
Ok(read_cargo_toml(path)?.package.name)
|
|
}
|
|
|
|
/// Check that the crate the given path is properly configured.
|
|
pub fn check_crate_config(path: &Path, step: &Step) -> Result<(), Error> {
|
|
let msg = format!("{}Checking crate configuration...", emoji::WRENCH);
|
|
PBAR.step(&step, &msg);
|
|
check_wasm_bindgen(path)?;
|
|
check_wasm_bindgen_test(path)?;
|
|
check_crate_type(path)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn check_wasm_bindgen(path: &Path) -> Result<(), Error> {
|
|
get_wasm_bindgen_version(path)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn check_wasm_bindgen_test(path: &Path) -> Result<(), Error> {
|
|
let expected_version = get_wasm_bindgen_version(path)?;
|
|
|
|
// Only do the version check if `wasm-bindgen-test` is actually a
|
|
// dependency. Not every crate needs to have tests!
|
|
if let Ok(actual_version) = get_wasm_bindgen_test_version(path) {
|
|
if expected_version != actual_version {
|
|
return Error::crate_config(&format!(
|
|
"The `wasm-bindgen-test` dependency version ({}) must match \
|
|
the `wasm-bindgen` dependency version ({}), but it does not.",
|
|
actual_version, expected_version
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn check_crate_type(path: &Path) -> Result<(), Error> {
|
|
if read_cargo_toml(path)?.lib.map_or(false, |lib| {
|
|
lib.crate_type
|
|
.map_or(false, |types| types.iter().any(|s| s == "cdylib"))
|
|
}) {
|
|
return Ok(());
|
|
}
|
|
Error::crate_config(
|
|
"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\"]"
|
|
)
|
|
}
|
|
|
|
fn get_dependency_version(
|
|
dependencies: Option<&HashMap<String, CargoDependency>>,
|
|
dependency: &str,
|
|
dependencies_section_name: &str,
|
|
version_suggestion: &str,
|
|
) -> Result<String, Error> {
|
|
if let Some(deps) = dependencies {
|
|
let dependency = normalize_dependency_name(dependency);
|
|
match deps.get(&dependency) {
|
|
Some(CargoDependency::Simple(version))
|
|
| Some(CargoDependency::Detailed(DetailedCargoDependency {
|
|
version: Some(version),
|
|
})) => Ok(version.clone()),
|
|
Some(CargoDependency::Detailed(DetailedCargoDependency { version: None })) => {
|
|
let msg = format!(
|
|
"\"{}\" dependency is missing its version number",
|
|
style(&dependency).bold().dim()
|
|
);
|
|
Err(Error::CrateConfig { message: msg })
|
|
}
|
|
None => {
|
|
let message = format!(
|
|
"Ensure that you have \"{}\" as a dependency in your Cargo.toml file:\n\
|
|
[{}]\n\
|
|
{} = \"{}\"",
|
|
style(&dependency).bold().dim(),
|
|
dependencies_section_name,
|
|
dependency,
|
|
version_suggestion
|
|
);
|
|
Err(Error::CrateConfig { message })
|
|
}
|
|
}
|
|
} else {
|
|
let message = String::from("Could not find crate dependencies");
|
|
Err(Error::CrateConfig { message })
|
|
}
|
|
}
|
|
|
|
/// Get the version of `wasm-bindgen` specified as a dependency.
|
|
pub fn get_wasm_bindgen_version(path: &Path) -> Result<String, Error> {
|
|
let toml = read_cargo_toml(path)?;
|
|
get_dependency_version(
|
|
toml.dependencies.as_ref(),
|
|
"wasm-bindgen",
|
|
"dependencies",
|
|
"0.2",
|
|
)
|
|
}
|
|
|
|
/// Get the version of `wasm-bindgen-test` specified as a dependency.
|
|
pub fn get_wasm_bindgen_test_version(path: &Path) -> Result<String, Error> {
|
|
let toml = read_cargo_toml(path)?;
|
|
get_dependency_version(
|
|
toml.dev_dependencies.as_ref(),
|
|
"wasm-bindgen-test",
|
|
"dev-dependencies",
|
|
"0.2",
|
|
)
|
|
}
|
|
|