forked from wasmerio/wasmer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Michael-F-Bryan
committed
Nov 17, 2022
1 parent
60f4ffa
commit e630c50
Showing
5 changed files
with
276 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
use std::{ | ||
fmt::{self, Display, Formatter}, | ||
process::{Command, Stdio}, | ||
str::FromStr, | ||
}; | ||
|
||
use anyhow::{Context, Error}; | ||
use clap::Parser; | ||
use wasmer_registry::{Bindings, PartialWapmConfig, ProgrammingLanguage}; | ||
|
||
/// Add a WAPM package's bindings to your application. | ||
#[derive(Debug, Parser)] | ||
pub struct Install { | ||
/// The registry to install bindings from. | ||
#[clap(long, env = "WAPM_REGISTRY")] | ||
registry: Option<String>, | ||
/// Add the JavaScript bindings using "npm install". | ||
#[clap(long, groups = &["bindings", "js"])] | ||
npm: bool, | ||
/// Add the JavaScript bindings using "yarn add". | ||
#[clap(long, groups = &["bindings", "js"])] | ||
yarn: bool, | ||
/// Install the package as a dev-dependency. | ||
#[clap(long, requires = "js")] | ||
dev: bool, | ||
/// Add the Python bindings using "pip install". | ||
#[clap(long, groups = &["bindings", "py"])] | ||
pip: bool, | ||
/// The packages to install (e.g. "wasmer/[email protected]" or "python/python") | ||
#[clap(parse(try_from_str))] | ||
packages: Vec<PackageSpecifier>, | ||
} | ||
|
||
impl Install { | ||
/// Execute [`Install`]. | ||
pub fn execute(&self) -> Result<(), Error> { | ||
anyhow::ensure!(!self.packages.is_empty(), "No packages specified"); | ||
|
||
let registry = self | ||
.registry() | ||
.context("Unable to determine which registry to use")?; | ||
|
||
let bindings = self.lookup_bindings(®istry)?; | ||
|
||
let mut cmd = self.target().command(&bindings); | ||
|
||
#[cfg(feature = "debug")] | ||
log::debug!("Running {cmd:?}"); | ||
|
||
let status = cmd | ||
.stdin(Stdio::null()) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.status() | ||
.with_context(|| { | ||
format!( | ||
"Unable to start \"{:?}\". Is it installed?", | ||
cmd.get_program() | ||
) | ||
})?; | ||
|
||
anyhow::ensure!(status.success(), "Command failed: {:?}", cmd); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn lookup_bindings(&self, registry: &str) -> Result<Vec<Bindings>, Error> { | ||
#[cfg(feature = "debug")] | ||
log::debug!("Querying WAPM for the bindings packages"); | ||
|
||
let mut bindings_to_install = Vec::new(); | ||
let language = self.target().language(); | ||
|
||
for pkg in &self.packages { | ||
let bindings = lookup_bindings_for_package(registry, pkg, &language) | ||
.with_context(|| format!("Unable to find bindings for {pkg}"))?; | ||
bindings_to_install.push(bindings); | ||
} | ||
|
||
Ok(bindings_to_install) | ||
} | ||
|
||
fn registry(&self) -> Result<String, Error> { | ||
match &self.registry { | ||
Some(r) => Ok(r.clone()), | ||
None => { | ||
let cfg = PartialWapmConfig::from_file() | ||
.map_err(Error::msg) | ||
.context("Unable to load WAPM's config file")?; | ||
Ok(cfg.registry.get_current_registry()) | ||
} | ||
} | ||
} | ||
|
||
fn target(&self) -> Target { | ||
match (self.pip, self.npm, self.yarn) { | ||
(true, false, false) => Target::Pip, | ||
(false, true, false) => Target::Npm { dev: self.dev }, | ||
(false, false, true) => Target::Yarn { dev: self.dev }, | ||
_ => unreachable!( | ||
"Clap should ensure at least one item in the \"bindings\" group is specified" | ||
), | ||
} | ||
} | ||
} | ||
|
||
fn lookup_bindings_for_package( | ||
registry: &str, | ||
pkg: &PackageSpecifier, | ||
language: &ProgrammingLanguage, | ||
) -> Result<Bindings, Error> { | ||
let all_bindings = | ||
wasmer_registry::list_bindings(®istry, &pkg.name, pkg.version.as_deref())?; | ||
|
||
match all_bindings.iter().find(|b| b.language == *language) { | ||
Some(b) => { | ||
#[cfg(feature = "debug")] | ||
{ | ||
let Bindings { url, generator, .. } = b; | ||
log::debug!("Found {pkg} bindings generated by {generator} at {url}"); | ||
} | ||
|
||
Ok(b.clone()) | ||
} | ||
None => { | ||
if all_bindings.is_empty() { | ||
anyhow::bail!("The package doesn't contain any bindings"); | ||
} else { | ||
todo!(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Copy, Clone)] | ||
enum Target { | ||
Pip, | ||
Yarn { dev: bool }, | ||
Npm { dev: bool }, | ||
} | ||
|
||
impl Target { | ||
fn language(self) -> ProgrammingLanguage { | ||
match self { | ||
Target::Pip => ProgrammingLanguage::PYTHON, | ||
Target::Yarn { .. } | Target::Npm { .. } => ProgrammingLanguage::JAVASCRIPT, | ||
} | ||
} | ||
|
||
/// Construct a command which we can run to install the packages. | ||
/// | ||
/// This deliberately runs the command using the OS shell instead of | ||
/// invoking the tool directly. That way we can handle when a version | ||
/// manager (e.g. `nvm` or `asdf`) replaces the tool with a script (e.g. | ||
/// `npm.cmd` or `yarn.ps1`). | ||
/// | ||
/// See <https://github.com/wasmerio/wapm-cli/issues/291> for more. | ||
fn command(self, packages: &[Bindings]) -> Command { | ||
let command_line = match self { | ||
Target::Pip => "pip install", | ||
Target::Yarn { dev: true } => "yarn add --dev", | ||
Target::Yarn { dev: false } => "yarn add", | ||
Target::Npm { dev: true } => "npm install --dev", | ||
Target::Npm { dev: false } => "npm install", | ||
}; | ||
let mut command_line = command_line.to_string(); | ||
|
||
for pkg in packages { | ||
command_line.push(' '); | ||
command_line.push_str(&pkg.url); | ||
} | ||
|
||
if cfg!(windows) { | ||
let mut cmd = Command::new("cmd"); | ||
cmd.arg("/C").arg(command_line); | ||
cmd | ||
} else { | ||
let mut cmd = Command::new("sh"); | ||
cmd.arg("-c").arg(command_line); | ||
cmd | ||
} | ||
} | ||
} | ||
|
||
/// The full name and optional version number for a WAPM package. | ||
#[derive(Debug)] | ||
struct PackageSpecifier { | ||
/// The package's full name (i.e. `wasmer/wasmer-pack` in | ||
/// `wasmer/[email protected]`). | ||
name: String, | ||
version: Option<String>, | ||
} | ||
|
||
impl FromStr for PackageSpecifier { | ||
type Err = Error; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
let (name, version) = match s.split_once('@') { | ||
Some((name, version)) => (name, Some(version)), | ||
None => (s, None), | ||
}; | ||
|
||
if !name | ||
.chars() | ||
.all(|c| c.is_ascii_alphanumeric() || "/_-".contains(c)) | ||
{ | ||
anyhow::bail!("Invalid package name"); | ||
} | ||
|
||
Ok(PackageSpecifier { | ||
name: name.to_string(), | ||
version: version.map(|s| s.to_string()), | ||
}) | ||
} | ||
} | ||
|
||
impl Display for PackageSpecifier { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
let PackageSpecifier { name, version } = self; | ||
|
||
write!(f, "{name}")?; | ||
if let Some(version) = version { | ||
write!(f, "@{version}")?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters