mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Make the cargo-run tool auto-install its wasm-opt build dependency (#4256)
* Allow auto downloading wasm-opt * Fix for mac * Review
This commit is contained in:
13
tools/cargo-run/internal/download/Cargo.toml
Normal file
13
tools/cargo-run/internal/download/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "cargo-run-internal-download"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cargo-run = { path = "../.." }
|
||||
ureq = { version = "3", default-features = false, features = ["rustls"] }
|
||||
sha2 = { version = "0.10" }
|
||||
flate2 = { version = "1", default-features = false, features = ["rust_backend"] }
|
||||
tar = { version = "0.4", default-features = false }
|
||||
163
tools/cargo-run/internal/download/src/main.rs
Normal file
163
tools/cargo-run/internal/download/src/main.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use cargo_run::Error;
|
||||
use flate2::read::GzDecoder;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
|
||||
pub fn usage() {
|
||||
eprintln!();
|
||||
eprintln!("USAGE:");
|
||||
eprintln!(" cargo run -p cargo-run-internal-download -- <URL> <SHA256> <OUT> \\");
|
||||
eprintln!(" [--extract] [--strip <N>] [--include <PREFIX>]...");
|
||||
eprintln!();
|
||||
eprintln!("Args:");
|
||||
eprintln!(" <URL> HTTPS source to download");
|
||||
eprintln!(" <SHA256> Expected SHA-256 of the response body (64 hex digits)");
|
||||
eprintln!(" <OUT> Destination file (default) or directory (with --extract)");
|
||||
eprintln!(" --extract Decompress the body as tar.gz and extract into <OUT>");
|
||||
eprintln!(" --strip <N> Strip N leading path components from each entry (requires --extract)");
|
||||
eprintln!(" --include <PREFIX> Only extract entries whose stripped path starts with PREFIX, repeatable (requires --extract)");
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
if let Err(e) = parse_args().inspect_err(|_| usage()).and_then(run) {
|
||||
eprintln!("Error: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
struct Args {
|
||||
url: String,
|
||||
sha256: String,
|
||||
out: PathBuf,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
File,
|
||||
ExtractTarGz { strip: usize, include: Vec<String> },
|
||||
}
|
||||
|
||||
fn run(Args { url, sha256, out, mode }: Args) -> Result<(), Error> {
|
||||
eprintln!("Downloading {url}");
|
||||
let mut body = Vec::new();
|
||||
ureq::get(&url)
|
||||
.call()
|
||||
.map_err(|e| Error::Io(io::Error::other(e), format!("HTTP GET {url}")))?
|
||||
.into_body()
|
||||
.into_with_config()
|
||||
.reader()
|
||||
.read_to_end(&mut body)
|
||||
.map_err(|e| Error::Io(io::Error::other(e), "reading response body".into()))?;
|
||||
|
||||
let actual = Sha256::digest(&body).iter().map(|b| format!("{b:02x}")).collect::<String>();
|
||||
if !actual.eq_ignore_ascii_case(&sha256) {
|
||||
eprintln!("SHA-256 mismatch:");
|
||||
eprintln!(" expected: {sha256}");
|
||||
eprintln!(" actual: {actual}");
|
||||
return Err(Error::Io(io::Error::other("SHA-256 mismatch"), "verifying download".into()));
|
||||
}
|
||||
|
||||
match mode {
|
||||
Mode::File => write_to_file(&body, &out),
|
||||
Mode::ExtractTarGz { strip, include } => extract_tar_gz(&body, &out, strip, &include),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_file(body: &[u8], path: &Path) -> Result<(), Error> {
|
||||
if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
|
||||
std::fs::create_dir_all(parent).map_err(|e| Error::Io(e, format!("creating '{}'", parent.display())))?;
|
||||
}
|
||||
std::fs::write(path, body).map_err(|e| Error::Io(e, format!("writing '{}'", path.display())))?;
|
||||
eprintln!("Wrote {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: support other compression/archive formats
|
||||
fn extract_tar_gz(body: &[u8], dir: &Path, strip: usize, include: &[String]) -> Result<(), Error> {
|
||||
std::fs::create_dir_all(dir).map_err(|e| Error::Io(e, format!("creating '{}'", dir.display())))?;
|
||||
let mut archive = tar::Archive::new(GzDecoder::new(body));
|
||||
archive.set_preserve_permissions(true);
|
||||
for entry in archive.entries().map_err(|e| Error::Io(e, "reading tar archive".into()))? {
|
||||
let mut entry = entry.map_err(|e| Error::Io(e, "reading tar entry".into()))?;
|
||||
let entry_path = entry.path().map_err(|e| Error::Io(e, "reading tar entry path".into()))?.into_owned();
|
||||
let Some(stripped) = strip_components(&entry_path, strip) else {
|
||||
continue;
|
||||
};
|
||||
if stripped.as_os_str().is_empty() || stripped.components().any(|c| matches!(c, Component::ParentDir | Component::Prefix(_) | Component::RootDir)) {
|
||||
continue;
|
||||
}
|
||||
if !include.is_empty() {
|
||||
let s = stripped.to_string_lossy().replace('\\', "/");
|
||||
if !include.iter().any(|p| s.starts_with(p.as_str())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let entry_type = entry.header().entry_type();
|
||||
if entry_type.is_symlink() || entry_type.is_hard_link() {
|
||||
continue;
|
||||
}
|
||||
let target = dir.join(&stripped);
|
||||
if let Some(parent) = target.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| Error::Io(e, format!("creating '{}'", parent.display())))?;
|
||||
}
|
||||
entry.unpack(&target).map_err(|e| Error::Io(e, format!("unpacking '{}'", target.display())))?;
|
||||
}
|
||||
eprintln!("Extracted into {}", dir.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn strip_components(path: &Path, n: usize) -> Option<PathBuf> {
|
||||
let mut comps = path.components();
|
||||
for _ in 0..n {
|
||||
comps.next()?;
|
||||
}
|
||||
Some(comps.as_path().to_path_buf())
|
||||
}
|
||||
|
||||
fn parse_args() -> Result<Args, Error> {
|
||||
fn arg_err(msg: impl Into<String>) -> Error {
|
||||
Error::Io(io::Error::new(io::ErrorKind::InvalidInput, msg.into()), "invalid arguments".into())
|
||||
}
|
||||
|
||||
let mut args = std::env::args().skip(1);
|
||||
let url = args.next().ok_or_else(|| arg_err("URL is required (first positional argument)"))?;
|
||||
let sha256 = args.next().ok_or_else(|| arg_err("SHA-256 is required (second positional argument)"))?;
|
||||
if sha256.len() != 64 || !sha256.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(arg_err("SHA-256 must be 64 hex digits"));
|
||||
}
|
||||
let out = PathBuf::from(args.next().ok_or_else(|| arg_err("OUT is required (third positional argument)"))?);
|
||||
|
||||
let mut extract = false;
|
||||
let mut strip: usize = 0;
|
||||
let mut include: Vec<String> = Vec::new();
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--extract" => extract = true,
|
||||
"--strip" => {
|
||||
let v = args.next().ok_or_else(|| arg_err("'--strip' requires a value"))?;
|
||||
strip = v.parse().map_err(|_| arg_err(format!("--strip must be a non-negative integer, got '{v}'")))?;
|
||||
}
|
||||
"--include" => include.push(args.next().ok_or_else(|| arg_err("'--include' requires a value"))?),
|
||||
other => return Err(arg_err(format!("unknown flag '{other}'"))),
|
||||
}
|
||||
}
|
||||
|
||||
let mode = if extract {
|
||||
Mode::ExtractTarGz { strip, include }
|
||||
} else {
|
||||
if strip != 0 {
|
||||
return Err(arg_err("--strip is only valid with --extract"));
|
||||
}
|
||||
if !include.is_empty() {
|
||||
return Err(arg_err("--include is only valid with --extract"));
|
||||
}
|
||||
Mode::File
|
||||
};
|
||||
|
||||
Ok(Args { url, sha256, out, mode })
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
use std::time::Duration;
|
||||
|
||||
const EXCLUDED_DIRECTORIES: &[&str] = &["target", ".git", "frontend/node_modules", "frontend/dist", "frontend/wrapper/pkg"];
|
||||
const EXCLUDED_DIRECTORIES: &[&str] = &["target", ".git", "frontend/node_modules", "frontend/dist", "frontend/wrapper/pkg", "tools"];
|
||||
const INCLUDED_EXTENSIONS: &[&str] = &["rs"];
|
||||
|
||||
const DEBOUNCE: Duration = Duration::from_millis(500);
|
||||
@@ -23,17 +23,16 @@ fn main() -> ExitCode {
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
println!("Watching for changes...");
|
||||
loop {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WatchGuard {
|
||||
struct WatchGuard {
|
||||
_debouncer: Debouncer<RecommendedWatcher, RecommendedCache>,
|
||||
}
|
||||
|
||||
pub fn watch(steps: impl IntoIterator<Item = Expression>) -> Result<WatchGuard, Error> {
|
||||
fn watch(steps: impl IntoIterator<Item = Expression>) -> Result<WatchGuard, Error> {
|
||||
let steps: Vec<Expression> = steps.into_iter().collect();
|
||||
let root = std::env::current_dir()
|
||||
.and_then(|p| p.canonicalize())
|
||||
|
||||
@@ -84,6 +84,14 @@ pub enum TerminalColor {
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
BrightBlack,
|
||||
BrightRed,
|
||||
BrightGreen,
|
||||
BrightYellow,
|
||||
BrightBlue,
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite,
|
||||
Reset,
|
||||
}
|
||||
impl TerminalColor {
|
||||
@@ -97,6 +105,14 @@ impl TerminalColor {
|
||||
Self::Magenta => "\x1b[35m",
|
||||
Self::Cyan => "\x1b[36m",
|
||||
Self::White => "\x1b[37m",
|
||||
Self::BrightBlack => "\x1b[90m",
|
||||
Self::BrightRed => "\x1b[91m",
|
||||
Self::BrightGreen => "\x1b[92m",
|
||||
Self::BrightYellow => "\x1b[93m",
|
||||
Self::BrightBlue => "\x1b[94m",
|
||||
Self::BrightMagenta => "\x1b[95m",
|
||||
Self::BrightCyan => "\x1b[96m",
|
||||
Self::BrightWhite => "\x1b[97m",
|
||||
Self::Reset => "\x1b[0m",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,14 @@ pub fn target_dir() -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install_dir() -> PathBuf {
|
||||
target_dir().join("cargo-run")
|
||||
}
|
||||
|
||||
pub fn bin_dir() -> PathBuf {
|
||||
install_dir().join("bin")
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("One or more requirements were not met")]
|
||||
|
||||
@@ -140,7 +140,7 @@ fn run_task(task: &Task) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
fn prepend_path() {
|
||||
let mut paths = vec![target_dir().join("cargo-run").join("bin")];
|
||||
let mut paths = vec![bin_dir()];
|
||||
if let Some(path) = std::env::var_os("PATH") {
|
||||
paths.extend(std::env::split_paths(&path));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::io::IsTerminal;
|
||||
use crate::cmd::prelude::*;
|
||||
use crate::*;
|
||||
|
||||
mod wasm_opt;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Requirement {
|
||||
command: &'static str,
|
||||
@@ -27,24 +29,17 @@ fn requirements(task: &Task) -> Vec<Requirement> {
|
||||
command: "rustc",
|
||||
args: &["--print", "target-libdir", "--target", "wasm32-unknown-unknown"],
|
||||
check: Check::Matches(&|out| std::path::Path::new(out.trim()).is_dir()),
|
||||
name: "Rust Wasm Toolchain",
|
||||
name: "Rust (Wasm Target)",
|
||||
install: "rustup target add wasm32-unknown-unknown".into(),
|
||||
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||
..Default::default()
|
||||
},
|
||||
Requirement {
|
||||
command: "cargo-about",
|
||||
args: &["--version"],
|
||||
name: "Cargo About",
|
||||
install: "cargo install cargo-about".into(),
|
||||
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||
..Default::default()
|
||||
},
|
||||
Requirement {
|
||||
command: "wasm-opt",
|
||||
args: &["--version"],
|
||||
name: "Wasm Opt",
|
||||
version: Some(">=130"),
|
||||
install: wasm_opt::install_action(),
|
||||
skip: Some(&|task| {
|
||||
matches!(task.target, Target::Cli)
|
||||
|| match task.profile {
|
||||
@@ -64,6 +59,14 @@ fn requirements(task: &Task) -> Vec<Requirement> {
|
||||
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||
..Default::default()
|
||||
},
|
||||
Requirement {
|
||||
command: "cargo-about",
|
||||
args: &["--version"],
|
||||
name: "Cargo About",
|
||||
install: "cargo install cargo-about".into(),
|
||||
skip: Some(&|task| matches!(task.target, Target::Cli)),
|
||||
..Default::default()
|
||||
},
|
||||
Requirement {
|
||||
command: "node",
|
||||
args: &["--version"],
|
||||
@@ -75,6 +78,7 @@ fn requirements(task: &Task) -> Vec<Requirement> {
|
||||
command: "cmake",
|
||||
args: &["--version"],
|
||||
name: "CMake",
|
||||
install: InstallAction::ManualInstructions("https://cmake.org/download/ or find it in your system's package manager"),
|
||||
skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -82,6 +86,7 @@ fn requirements(task: &Task) -> Vec<Requirement> {
|
||||
command: "ninja",
|
||||
args: &["--version"],
|
||||
name: "Ninja",
|
||||
install: InstallAction::ManualInstructions("https://github.com/ninja-build/ninja/releases or find it in your system's package manager"),
|
||||
skip: Some(&|task| !matches!(task.target, Target::Desktop) || cfg!(target_os = "linux")),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -97,7 +102,7 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
eprintln!("Checking Requirements:");
|
||||
|
||||
let mut installable: Vec<Requirement> = Vec::new();
|
||||
let mut failures: Vec<String> = Vec::new();
|
||||
let mut manual: Vec<(Requirement, String)> = Vec::new();
|
||||
|
||||
for dep in requirements(task) {
|
||||
match cmd(dep.command, dep.args.iter().copied()).output_unchecked() {
|
||||
@@ -114,15 +119,15 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
Some(version) if req.matches(&version) => eprintln!(" ✓ {} ({version})", dep.name),
|
||||
Some(version) => {
|
||||
eprintln!(" ✗ {} (found {version}, requires {req_str})", dep.name);
|
||||
if dep.install.is_some() {
|
||||
if dep.install.is_auto_installable() {
|
||||
installable.push(dep);
|
||||
} else {
|
||||
failures.push(format!("{}: version mismatch (found {version}, requires {req_str})", dep.name));
|
||||
manual.push((dep, format!("version mismatch (found {version}, requires {req_str})")));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!(" ✗ {} (could not parse version from '{line}')", dep.name);
|
||||
failures.push(format!("{}: could not parse version from '{line}'", dep.name));
|
||||
manual.push((dep, format!("could not parse version from '{line}'")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,8 +136,10 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
Check::Matches(check) => {
|
||||
if !check(stdout.to_string()) {
|
||||
eprintln!(" ✗ {} - check failed", dep.name);
|
||||
if dep.install.is_some() {
|
||||
if dep.install.is_auto_installable() {
|
||||
installable.push(dep);
|
||||
} else {
|
||||
manual.push((dep, "check failed".into()));
|
||||
}
|
||||
} else {
|
||||
eprintln!(" ✓ {}", dep.name);
|
||||
@@ -143,18 +150,18 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
Ok(output) => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
eprintln!(" ✗ {} - command failed: {}", dep.name, stderr.trim());
|
||||
if dep.install.is_some() {
|
||||
if dep.install.is_auto_installable() {
|
||||
installable.push(dep);
|
||||
} else {
|
||||
failures.push(format!("{}: not installed or not working", dep.name));
|
||||
manual.push((dep.clone(), format!("`{}` not installed or not working", dep.command)));
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!(" ✗ {} - not found", dep.name);
|
||||
if dep.install.is_some() {
|
||||
if dep.install.is_auto_installable() {
|
||||
installable.push(dep);
|
||||
} else {
|
||||
failures.push(format!("{}: not found in PATH", dep.name));
|
||||
manual.push((dep.clone(), format!("`{}` not found in PATH", dep.command)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,20 +169,20 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
|
||||
eprintln!();
|
||||
|
||||
if installable.is_empty() && failures.is_empty() {
|
||||
if installable.is_empty() && manual.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let total = installable.len() + failures.len();
|
||||
let total = installable.len() + manual.len();
|
||||
eprintln!("{total} requirement{} not met:", if total > 1 { "s" } else { "" });
|
||||
for dep in &installable {
|
||||
eprintln!(" - {}", dep.name);
|
||||
}
|
||||
for msg in &failures {
|
||||
eprintln!(" - {msg}");
|
||||
for (dep, msg) in &manual {
|
||||
eprintln!(" - {}: {msg}", dep.name);
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
if !manual.is_empty() {
|
||||
eprintln!();
|
||||
eprintln!("See: https://graphite.art/volunteer/guide/project-setup/");
|
||||
}
|
||||
@@ -187,7 +194,7 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if installable.is_empty() && failures.is_empty() {
|
||||
if installable.is_empty() && manual.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -195,13 +202,17 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
eprintln!();
|
||||
eprintln!("The following can be installed automatically:");
|
||||
for dep in &installable {
|
||||
eprintln!(" {}: {}", dep.name, dep.install.description());
|
||||
match &dep.install {
|
||||
InstallAction::Command(cmd) => eprintln!(" - {}: {}", dep.name, cmd),
|
||||
InstallAction::Expression { description, .. } => eprintln!(" - {description}"),
|
||||
InstallAction::None | InstallAction::ManualInstructions(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
eprintln!();
|
||||
if installable.len() == 1 {
|
||||
eprint!("Install it now? [Y/n] ");
|
||||
eprint!("Install it now? [y/N] ");
|
||||
} else {
|
||||
eprint!("Install them now? [Y/n] ");
|
||||
eprint!("Install them now? [y/N] ");
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
@@ -209,55 +220,54 @@ pub fn check(task: &Task) -> Result<(), Error> {
|
||||
let input = input.trim();
|
||||
|
||||
if input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes") {
|
||||
let mut successfully_installed = Vec::new();
|
||||
|
||||
for (i, dep) in installable.iter().enumerate() {
|
||||
for dep in installable.into_iter() {
|
||||
eprintln!("Installing {}...", dep.name);
|
||||
match &dep.install {
|
||||
InstallAction::Command(install_cmd) => {
|
||||
let parts: Vec<&str> = install_cmd.split_whitespace().collect();
|
||||
let expr = cmd(parts[0], parts[1..].iter().copied()).unchecked();
|
||||
match Expression::run(&expr) {
|
||||
Ok(output) if output.status.success() => successfully_installed.push(i),
|
||||
Ok(_) => eprintln!("Failed to install {}", dep.name),
|
||||
Ok(output) if !output.status.success() => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
manual.push((dep, format!("installation command failed: {}", stderr.trim())));
|
||||
}
|
||||
Err(e) => return Err(Error::Command(e)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
InstallAction::Function { function, .. } => {
|
||||
if let Err(e) = function() {
|
||||
InstallAction::Expression { expression, .. } => {
|
||||
if let Err(e) = expression.clone().run() {
|
||||
eprintln!("{e}");
|
||||
eprintln!("Failed to install {}", dep.name);
|
||||
} else {
|
||||
successfully_installed.push(i);
|
||||
manual.push((dep, format!("failed to install ({e})")));
|
||||
}
|
||||
}
|
||||
InstallAction::None => unreachable!(),
|
||||
InstallAction::None | InstallAction::ManualInstructions(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in successfully_installed.into_iter().rev() {
|
||||
installable.remove(i);
|
||||
if !manual.is_empty() {
|
||||
eprintln!();
|
||||
eprintln!("Please install the following dependenc{}:", if manual.len() == 1 { "y" } else { "ies" });
|
||||
for (dep, msg) in &manual {
|
||||
match dep.install {
|
||||
InstallAction::ManualInstructions(instructions) => eprintln!(" - {}: {}", dep.name, instructions),
|
||||
_ => eprintln!(" - {}: {}", dep.name, msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
if (!manual.is_empty()) && is_interactive {
|
||||
eprintln!();
|
||||
eprintln!("The following requirements must be resolved manually:");
|
||||
for msg in &failures {
|
||||
eprintln!(" - {msg}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!installable.is_empty() || !failures.is_empty()) && is_interactive {
|
||||
eprintln!();
|
||||
eprintln!("Continue without resolving these requirements? [y/N]");
|
||||
eprintln!("Attempt to continue regardless of unmet requirements? [y/N]");
|
||||
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).map_err(|e| Error::Io(e, "Failed to read from stdin".into()))?;
|
||||
let input = input.trim();
|
||||
|
||||
if input.eq_ignore_ascii_case("n") || input.eq_ignore_ascii_case("no") {
|
||||
if !input.eq_ignore_ascii_case("y") && !input.eq_ignore_ascii_case("yes") {
|
||||
return Err(Error::RequirementsNotMet);
|
||||
}
|
||||
}
|
||||
@@ -300,24 +310,16 @@ enum InstallAction {
|
||||
#[default]
|
||||
None,
|
||||
Command(&'static str),
|
||||
#[expect(dead_code)] // TODO: Remove after followup pr landed
|
||||
Function {
|
||||
description: &'static str,
|
||||
function: &'static dyn Fn() -> Result<(), Error>,
|
||||
Expression {
|
||||
description: String,
|
||||
expression: Expression,
|
||||
},
|
||||
ManualInstructions(&'static str),
|
||||
}
|
||||
|
||||
impl InstallAction {
|
||||
fn is_some(&self) -> bool {
|
||||
!matches!(self, InstallAction::None)
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
InstallAction::None => "",
|
||||
InstallAction::Command(cmd) => cmd,
|
||||
InstallAction::Function { description, .. } => description,
|
||||
}
|
||||
fn is_auto_installable(&self) -> bool {
|
||||
!matches!(self, InstallAction::None | InstallAction::ManualInstructions(_))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
46
tools/cargo-run/src/requirements/wasm_opt.rs
Normal file
46
tools/cargo-run/src/requirements/wasm_opt.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use super::InstallAction;
|
||||
use crate::cmd::prelude::*;
|
||||
use crate::{install_dir, workspace_dir};
|
||||
|
||||
/// Pinned Binaryen release used by [`install_action`].
|
||||
/// NOTICE: keep in sync with the `BINARYEN_VERSION` pinned across the CI workflows, and update [`SHA256`] below.
|
||||
const VERSION: &str = "130";
|
||||
const SHA256: &[(&str, &str)] = &[
|
||||
("x86_64-windows", "cc09c874f4332d00aa32ab72745a9b98c9a172f795762f21d03e70638a3f7f4c"),
|
||||
("arm64-windows", "b18c9cbe000562b1ee5d9cb60146616a949aca504903ad63f27fd9fd679898a7"),
|
||||
("arm64-macos", "79d3ab9f417d9e215f15f598f523d001a7d9ac1e59367e5c869fbdabd1cba72e"),
|
||||
("x86_64-macos", "d3e2d1235b70c93c54b52eabc1625ea960965152218754f1f4eeb0f873c48e03"),
|
||||
("x86_64-linux", "0a18362361ad05465118cd8eeb72edaeec89de6894bc283576ef4e07aa3babcc"),
|
||||
("aarch64-linux", "e6ae6e09ac40f4e14bc5be6f687c58e2995c84170013975fa641809dd3b480a0"),
|
||||
];
|
||||
|
||||
fn url_for(platform: &str) -> String {
|
||||
format!("https://github.com/WebAssembly/binaryen/releases/download/version_{VERSION}/binaryen-version_{VERSION}-{platform}.tar.gz")
|
||||
}
|
||||
|
||||
pub fn install_action() -> InstallAction {
|
||||
let platform = match (std::env::consts::OS, std::env::consts::ARCH) {
|
||||
("windows", "x86_64") => "x86_64-windows",
|
||||
("windows", "aarch64") => "arm64-windows",
|
||||
("macos", "aarch64") => "arm64-macos",
|
||||
("macos", "x86_64") => "x86_64-macos",
|
||||
("linux", "x86_64") => "x86_64-linux",
|
||||
("linux", "aarch64") => "aarch64-linux",
|
||||
_ => return InstallAction::None,
|
||||
};
|
||||
let url = url_for(platform);
|
||||
let Some(sha256) = SHA256.iter().find_map(|(p, s)| (*p == platform).then_some(*s)) else {
|
||||
return InstallAction::None;
|
||||
};
|
||||
let out = install_dir().to_string_lossy().into_owned();
|
||||
let description = format!("Download wasm-opt {VERSION} from {url} (sha256 {sha256})");
|
||||
|
||||
let args = [&url, sha256, &out, "--extract", "--strip", "1", "--include", "bin/wasm-opt"];
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let args = args.into_iter().chain(["--include", "lib/libbinaryen.dylib"]).collect::<Vec<_>>();
|
||||
|
||||
let expression = utils::internal("download").args(args).dir(workspace_dir());
|
||||
|
||||
InstallAction::Expression { description, expression }
|
||||
}
|
||||
Reference in New Issue
Block a user