From 3d5f853b031e9b913f42e7dc556a86e11b76a9a5 Mon Sep 17 00:00:00 2001 From: Piepmatz Date: Sat, 30 Nov 2024 14:57:11 +0100 Subject: [PATCH] Start to Add WASM Support Again (#14418) # Description The [nushell/demo](https://github.com/nushell/demo) project successfully demonstrated running Nushell in the browser using WASM. However, the current version of Nushell cannot be easily built for the `wasm32-unknown-unknown` target, the default for `wasm-bindgen`. This PR introduces initial support for the `wasm32-unknown-unknown` target by disabling OS-dependent features such as filesystem access, IO, and platform/system-specific functionality. This separation is achieved using a new `os` feature in the following crates: - `nu-cmd-lang` - `nu-command` - `nu-engine` - `nu-protocol` The `os` feature includes all functionality that interacts with an operating system. It is enabled by default, but can be disabled using `--no-default-features`. All crates that depend on these core crates now use `--no-default-features` to allow compilation for WASM. To demonstrate compatibility, the following script builds all crates expected to work with WASM. Direct user interaction, running external commands, working with plugins, and features requiring `openssl` are out of scope for now due to their complexity or reliance on C libraries, which are difficult to compile and link in a WASM environment. ```nushell [ # compatible crates "nu-cmd-base", "nu-cmd-extra", "nu-cmd-lang", "nu-color-config", "nu-command", "nu-derive-value", "nu-engine", "nu-glob", "nu-json", "nu-parser", "nu-path", "nu-pretty-hex", "nu-protocol", "nu-std", "nu-system", "nu-table", "nu-term-grid", "nu-utils", "nuon" ] | each {cargo build -p $in --target wasm32-unknown-unknown --no-default-features} ``` ## Caveats This PR has a few caveats: 1. **`miette` and `terminal-size` Dependency Issue** `miette` depends on `terminal-size`, which uses `rustix` when the target is not Windows. However, `rustix` requires `std::os::unix`, which is unavailable in WASM. To address this, I opened a [PR](https://github.com/eminence/terminal-size/pull/68) for `terminal-size` to conditionally compile `rustix` only when the target is Unix. For now, the `Cargo.toml` includes patches to: - Use my forked version of `terminal-size`. - ~~Use an unreleased version of `miette` that depends on `terminal-size@0.4`.~~ These patches are temporary and can be removed once the upstream changes are merged and released. 2. **Test Output Adjustments** Due to the slight bump in the `miette` version, one test required adjustments to accommodate minor formatting changes in the error output, such as shifted newlines. # User-Facing Changes This shouldn't break anything but allows using some crates for targeting `wasm32-unknown-unknown` to revive the demo page eventually. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` I did not add any extra tests, I just checked that compiling works, also when using the host target but unselecting the `os` feature. # After Submitting ~~Breaking the wasm support can be easily done by adding some `use`s or by adding a new dependency, we should definitely add some CI that also at least builds against wasm to make sure that building for it keep working.~~ I added a job to build wasm. --------- Co-authored-by: Ian Manske --- .github/workflows/ci.yml | 31 +++++++ Cargo.lock | 10 +- Cargo.toml | 1 + crates/nu-cli/Cargo.toml | 4 +- crates/nu-cmd-base/Cargo.toml | 4 +- crates/nu-cmd-extra/Cargo.toml | 6 +- crates/nu-cmd-lang/Cargo.toml | 17 +++- .../nu-cmd-lang/src/core_commands/describe.rs | 1 + crates/nu-cmd-lang/src/core_commands/do_.rs | 17 +++- .../nu-cmd-lang/src/core_commands/ignore.rs | 1 + .../nu-cmd-lang/src/core_commands/version.rs | 40 ++++---- crates/nu-cmd-lang/src/lib.rs | 1 + crates/nu-color-config/Cargo.toml | 4 +- crates/nu-command/Cargo.toml | 91 ++++++++++++++----- crates/nu-command/src/debug/info.rs | 5 + crates/nu-command/src/debug/inspect.rs | 4 +- crates/nu-command/src/default_context.rs | 37 +++++--- crates/nu-command/src/env/config/config_.rs | 80 +++++++++++++++- .../nu-command/src/env/config/config_env.rs | 61 +------------ crates/nu-command/src/env/config/config_nu.rs | 61 +------------ .../nu-command/src/experimental/is_admin.rs | 6 ++ crates/nu-command/src/filesystem/save.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/tee.rs | 9 +- crates/nu-command/src/lib.rs | 9 ++ crates/nu-command/src/network/mod.rs | 4 + crates/nu-command/src/viewers/griddle.rs | 5 +- crates/nu-command/src/viewers/table.rs | 24 ++++- crates/nu-engine/Cargo.toml | 14 ++- crates/nu-engine/src/eval_ir.rs | 11 ++- crates/nu-parser/Cargo.toml | 6 +- crates/nu-path/src/tilde.rs | 8 ++ crates/nu-path/src/trailing_slash.rs | 7 ++ crates/nu-plugin/Cargo.toml | 4 +- crates/nu-protocol/Cargo.toml | 13 ++- crates/nu-protocol/src/errors/shell_error.rs | 11 +++ crates/nu-protocol/src/lib.rs | 2 + .../nu-protocol/src/pipeline/byte_stream.rs | 34 ++++++- crates/nu-std/Cargo.toml | 4 +- crates/nu-table/Cargo.toml | 6 +- crates/nu-term-grid/Cargo.toml | 2 +- crates/nu-utils/Cargo.toml | 5 + crates/nu-utils/src/filesystem.rs | 1 + crates/nu-utils/src/lib.rs | 2 +- crates/nu-utils/src/utils.rs | 16 +++- crates/nu_plugin_custom_values/Cargo.toml | 2 +- crates/nuon/Cargo.toml | 6 +- toolkit.nu | 34 +++++++ 48 files changed, 490 insertions(+), 234 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index badadb56ae..f3daa9a466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,3 +162,34 @@ jobs: else echo "no changes in working directory"; fi + + build-wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.7 + + - name: Setup Rust toolchain and cache + uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 + + - name: Add wasm32-unknown-unknown target + run: rustup target add wasm32-unknown-unknown + + - run: cargo build -p nu-cmd-base --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-cmd-extra --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-cmd-lang --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-color-config --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-command --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-derive-value --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-engine --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-glob --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-json --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-parser --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-path --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-pretty-hex --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-protocol --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-std --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-system --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-table --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-term-grid --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-utils --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nuon --no-default-features --target wasm32-unknown-unknown diff --git a/Cargo.lock b/Cargo.lock index 1e4b123f81..a5399b9ddc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2647,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -3368,6 +3368,7 @@ dependencies = [ "fancy-regex", "filesize", "filetime", + "getrandom", "human-date-parser", "indexmap", "indicatif", @@ -3755,6 +3756,7 @@ dependencies = [ name = "nu-utils" version = "0.100.1" dependencies = [ + "crossterm 0.28.1", "crossterm_winapi", "fancy-regex", "log", @@ -6847,9 +6849,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", "windows-sys 0.59.0", @@ -7874,7 +7876,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 86d863f76c..c7bafe89d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,6 +134,7 @@ quickcheck = "1.0" quickcheck_macros = "1.0" quote = "1.0" rand = "0.8" +getrandom = "0.2" # pick same version that rand requires rand_chacha = "0.3.1" ratatui = "0.26" rayon = "1.10" diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index b91314ddcf..993bb1ac72 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -19,11 +19,11 @@ tempfile = { workspace = true } [dependencies] nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", features = ["os"] } nu-path = { path = "../nu-path", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.100.1", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["os"] } nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-ansi-term = { workspace = true } diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index e47dc12641..fb36998bbd 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -13,10 +13,10 @@ version = "0.100.1" workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } indexmap = { workspace = true } miette = { workspace = true } diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index eec817059b..b54597f35a 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -17,12 +17,12 @@ workspace = true [dependencies] nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-json = { version = "0.100.1", path = "../nu-json" } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-pretty-hex = { version = "0.100.1", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } # Potential dependencies for extras heck = { workspace = true } diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 6e8cb824ba..00ba449785 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -15,10 +15,10 @@ bench = false workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-parser = { path = "../nu-parser", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } itertools = { workspace = true } shadow-rs = { version = "0.36", default-features = false } @@ -27,6 +27,17 @@ shadow-rs = { version = "0.36", default-features = false } shadow-rs = { version = "0.36", default-features = false } [features] +default = ["os"] +os = [ + "nu-engine/os", + "nu-protocol/os", + "nu-utils/os", +] +plugin = [ + "nu-protocol/plugin", + "os", +] + mimalloc = [] trash-support = [] sqlite = [] diff --git a/crates/nu-cmd-lang/src/core_commands/describe.rs b/crates/nu-cmd-lang/src/core_commands/describe.rs index ec98dc896f..375a840b16 100644 --- a/crates/nu-cmd-lang/src/core_commands/describe.rs +++ b/crates/nu-cmd-lang/src/core_commands/describe.rs @@ -169,6 +169,7 @@ fn run( let origin = match stream.source() { ByteStreamSource::Read(_) => "unknown", ByteStreamSource::File(_) => "file", + #[cfg(feature = "os")] ByteStreamSource::Child(_) => "external", }; diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 81ea22ad39..05d22c6cea 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -1,9 +1,8 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env}; -use nu_protocol::{ - engine::Closure, - process::{ChildPipe, ChildProcess}, - ByteStream, ByteStreamSource, OutDest, -}; +#[cfg(feature = "os")] +use nu_protocol::process::{ChildPipe, ChildProcess}; +use nu_protocol::{engine::Closure, ByteStream, ByteStreamSource, OutDest}; + use std::{ io::{Cursor, Read}, thread, @@ -119,6 +118,13 @@ impl Command for Do { match result { Ok(PipelineData::ByteStream(stream, metadata)) if capture_errors => { let span = stream.span(); + #[cfg(not(feature = "os"))] + return Err(ShellError::DisabledOsSupport { + msg: "Cannot create a thread to receive stdout message.".to_string(), + span: Some(span), + }); + + #[cfg(feature = "os")] match stream.into_child() { Ok(mut child) => { // Use a thread to receive stdout message. @@ -196,6 +202,7 @@ impl Command for Do { OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value ) => { + #[cfg(feature = "os")] if let ByteStreamSource::Child(child) = stream.source_mut() { child.ignore_error(true); } diff --git a/crates/nu-cmd-lang/src/core_commands/ignore.rs b/crates/nu-cmd-lang/src/core_commands/ignore.rs index 382dc4fedd..b28a9582a3 100644 --- a/crates/nu-cmd-lang/src/core_commands/ignore.rs +++ b/crates/nu-cmd-lang/src/core_commands/ignore.rs @@ -35,6 +35,7 @@ impl Command for Ignore { mut input: PipelineData, ) -> Result { if let PipelineData::ByteStream(stream, _) = &mut input { + #[cfg(feature = "os")] if let ByteStreamSource::Child(child) = stream.source_mut() { child.ignore_error(true); } diff --git a/crates/nu-cmd-lang/src/core_commands/version.rs b/crates/nu-cmd-lang/src/core_commands/version.rs index f5716be6b3..9ebc43dee2 100644 --- a/crates/nu-cmd-lang/src/core_commands/version.rs +++ b/crates/nu-cmd-lang/src/core_commands/version.rs @@ -116,24 +116,30 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result>(); + #[cfg(not(feature = "plugin"))] + let _ = engine_state; - record.push( - "installed_plugins", - Value::string(installed_plugins.join(", "), span), - ); + #[cfg(feature = "plugin")] + { + // Get a list of plugin names and versions if present + let installed_plugins = engine_state + .plugins() + .iter() + .map(|x| { + let name = x.identity().name(); + if let Some(version) = x.metadata().and_then(|m| m.version) { + format!("{name} {version}") + } else { + name.into() + } + }) + .collect::>(); + + record.push( + "installed_plugins", + Value::string(installed_plugins.join(", "), span), + ); + } Ok(Value::record(record, span).into_pipeline_data()) } diff --git a/crates/nu-cmd-lang/src/lib.rs b/crates/nu-cmd-lang/src/lib.rs index 41022cc231..1a46c0a350 100644 --- a/crates/nu-cmd-lang/src/lib.rs +++ b/crates/nu-cmd-lang/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "os"), allow(unused))] #![doc = include_str!("../README.md")] mod core_commands; mod default_context; diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index abf95023cc..19faec8e1b 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -14,8 +14,8 @@ bench = false workspace = true [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-json = { path = "../nu-json", version = "0.100.1" } nu-ansi-term = { workspace = true } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 53e39353c0..ce1c336153 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -18,17 +18,17 @@ workspace = true [dependencies] nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-glob = { path = "../nu-glob", version = "0.100.1" } nu-json = { path = "../nu-json", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } nu-system = { path = "../nu-system", version = "0.100.1" } nu-table = { path = "../nu-table", version = "0.100.1" } nu-term-grid = { path = "../nu-term-grid", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } nu-ansi-term = { workspace = true } nuon = { path = "../nuon", version = "0.100.1" } @@ -43,7 +43,7 @@ chardetng = { workspace = true } chrono = { workspace = true, features = ["std", "unstable-locales", "clock"], default-features = false } chrono-humanize = { workspace = true } chrono-tz = { workspace = true } -crossterm = { workspace = true } +crossterm = { workspace = true, optional = true } csv = { workspace = true } dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] } digest = { workspace = true, default-features = false } @@ -61,19 +61,20 @@ lscolors = { workspace = true, default-features = false, features = ["nu-ansi-te md5 = { workspace = true } mime = { workspace = true } mime_guess = { workspace = true } -multipart-rs = { workspace = true } -native-tls = { workspace = true } -notify-debouncer-full = { workspace = true, default-features = false } +multipart-rs = { workspace = true, optional = true } +native-tls = { workspace = true, optional = true } +notify-debouncer-full = { workspace = true, default-features = false, optional = true } num-format = { workspace = true } num-traits = { workspace = true } oem_cp = { workspace = true } -open = { workspace = true } -os_pipe = { workspace = true } +open = { workspace = true, optional = true } +os_pipe = { workspace = true, optional = true } pathdiff = { workspace = true } percent-encoding = { workspace = true } print-positions = { workspace = true } quick-xml = { workspace = true } -rand = { workspace = true } +rand = { workspace = true, optional = true } +getrandom = { workspace = true, optional = true } rayon = { workspace = true } regex = { workspace = true } roxmltree = { workspace = true } @@ -89,26 +90,26 @@ tabled = { workspace = true, features = ["ansi"], default-features = false } titlecase = { workspace = true } toml = { workspace = true, features = ["preserve_order"] } unicode-segmentation = { workspace = true } -ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] } +ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json"] } url = { workspace = true } -uu_cp = { workspace = true } -uu_mkdir = { workspace = true } -uu_mktemp = { workspace = true } -uu_mv = { workspace = true } -uu_touch = { workspace = true } -uu_uname = { workspace = true } -uu_whoami = { workspace = true } -uuid = { workspace = true, features = ["v4"] } +uu_cp = { workspace = true, optional = true } +uu_mkdir = { workspace = true, optional = true } +uu_mktemp = { workspace = true, optional = true } +uu_mv = { workspace = true, optional = true } +uu_touch = { workspace = true, optional = true } +uu_uname = { workspace = true, optional = true } +uu_whoami = { workspace = true, optional = true } +uuid = { workspace = true, features = ["v4"], optional = true } v_htmlescape = { workspace = true } wax = { workspace = true } -which = { workspace = true } +which = { workspace = true, optional = true } unicode-width = { workspace = true } data-encoding = { version = "2.6.0", features = ["alloc"] } [target.'cfg(windows)'.dependencies] winreg = { workspace = true } -[target.'cfg(not(windows))'.dependencies] +[target.'cfg(all(not(windows), not(target_arch = "wasm32")))'.dependencies] uucore = { workspace = true, features = ["mode"] } [target.'cfg(unix)'.dependencies] @@ -134,7 +135,53 @@ features = [ workspace = true [features] -plugin = ["nu-parser/plugin"] +default = ["os"] +os = [ + # include other features + "js", + "network", + "nu-protocol/os", + "nu-utils/os", + + # os-dependant dependencies + "crossterm", + "notify-debouncer-full", + "open", + "os_pipe", + "uu_cp", + "uu_mkdir", + "uu_mktemp", + "uu_mv", + "uu_touch", + "uu_uname", + "uu_whoami", + "which", +] + +# The dependencies listed below need 'getrandom'. +# They work with JS (usually with wasm-bindgen) or regular OS support. +# Hence they are also put under the 'os' feature to avoid repetition. +js = [ + "getrandom", + "getrandom/js", + "rand", + "uuid", +] + +# These dependencies require networking capabilities, especially the http +# interface requires openssl which is not easy to embed into wasm, +# using rustls could solve this issue. +network = [ + "multipart-rs", + "native-tls", + "ureq/native-tls", + "uuid", +] + +plugin = [ + "nu-parser/plugin", + "os", +] sqlite = ["rusqlite"] trash-support = ["trash"] diff --git a/crates/nu-command/src/debug/info.rs b/crates/nu-command/src/debug/info.rs index 2a680ae20d..4912da8a1d 100644 --- a/crates/nu-command/src/debug/info.rs +++ b/crates/nu-command/src/debug/info.rs @@ -177,4 +177,9 @@ fn get_thread_id() -> u64 { { nix::sys::pthread::pthread_self() as u64 } + #[cfg(target_arch = "wasm32")] + { + // wasm doesn't have any threads accessible, so we return 0 as a fallback + 0 + } } diff --git a/crates/nu-command/src/debug/inspect.rs b/crates/nu-command/src/debug/inspect.rs index 4a9f7bc4a5..f8ef63ce02 100644 --- a/crates/nu-command/src/debug/inspect.rs +++ b/crates/nu-command/src/debug/inspect.rs @@ -1,6 +1,6 @@ use super::inspect_table; -use crossterm::terminal::size; use nu_engine::command_prelude::*; +use nu_utils::terminal_size; #[derive(Clone)] pub struct Inspect; @@ -38,7 +38,7 @@ impl Command for Inspect { let original_input = input_val.clone(); let description = input_val.get_type().to_string(); - let (cols, _rows) = size().unwrap_or((0, 0)); + let (cols, _rows) = terminal_size().unwrap_or((0, 0)); let table = inspect_table::build_table(input_val, description, cols as usize); diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index fcca72b2f2..0a4075c532 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -27,6 +27,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { } // Filters + #[cfg(feature = "rand")] + bind_command! { + Shuffle + } bind_command! { All, Any, @@ -72,7 +76,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Rename, Reverse, Select, - Shuffle, Skip, SkipUntil, SkipWhile, @@ -114,6 +117,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { }; // System + #[cfg(feature = "os")] bind_command! { Complete, External, @@ -161,17 +165,20 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ViewSpan, }; - #[cfg(windows)] + #[cfg(all(feature = "os", windows))] bind_command! { RegistryQuery } - #[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "macos", - target_os = "windows" + #[cfg(all( + feature = "os", + any( + target_os = "android", + target_os = "linux", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "windows" + ) ))] bind_command! { Ps }; @@ -219,6 +226,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { }; // FileSystem + #[cfg(feature = "os")] bind_command! { Cd, Ls, @@ -237,6 +245,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { }; // Platform + #[cfg(feature = "os")] bind_command! { Ansi, AnsiLink, @@ -255,7 +264,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Whoami, }; - #[cfg(unix)] + #[cfg(all(unix, feature = "os"))] bind_command! { ULimit }; // Date @@ -380,6 +389,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { } // Network + #[cfg(feature = "network")] bind_command! { Http, HttpDelete, @@ -389,6 +399,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { HttpPost, HttpPut, HttpOptions, + Port, + } + bind_command! { Url, UrlBuildQuery, UrlSplitQuery, @@ -396,10 +409,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { UrlEncode, UrlJoin, UrlParse, - Port, } // Random + #[cfg(feature = "rand")] bind_command! { Random, RandomBool, diff --git a/crates/nu-command/src/env/config/config_.rs b/crates/nu-command/src/env/config/config_.rs index 207c8c8651..71afd57c5a 100644 --- a/crates/nu-command/src/env/config/config_.rs +++ b/crates/nu-command/src/env/config/config_.rs @@ -1,4 +1,6 @@ -use nu_engine::{command_prelude::*, get_full_help}; +use nu_cmd_base::util::get_editor; +use nu_engine::{command_prelude::*, env_to_strings, get_full_help}; +use nu_system::ForegroundChild; #[derive(Clone)] pub struct ConfigMeta; @@ -36,3 +38,79 @@ impl Command for ConfigMeta { vec!["options", "setup"] } } + +#[cfg(not(feature = "os"))] +pub(super) fn start_editor( + _: &'static str, + _: &EngineState, + _: &mut Stack, + call: &Call, +) -> Result { + Err(ShellError::DisabledOsSupport { + msg: "Running external commands is not available without OS support.".to_string(), + span: Some(call.head), + }) +} + +#[cfg(feature = "os")] +pub(super) fn start_editor( + config_path: &'static str, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + // Find the editor executable. + let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; + let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; + let cwd = engine_state.cwd(Some(stack))?; + let editor_executable = + crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(ShellError::ExternalCommand { + label: format!("`{editor_name}` not found"), + help: "Failed to find the editor executable".into(), + span: call.head, + })?; + + let Some(config_path) = engine_state.get_config_path(config_path) else { + return Err(ShellError::GenericError { + error: format!("Could not find $nu.{config_path}"), + msg: format!("Could not find $nu.{config_path}"), + span: None, + help: None, + inner: vec![], + }); + }; + let config_path = config_path.to_string_lossy().to_string(); + + // Create the command. + let mut command = std::process::Command::new(editor_executable); + + // Configure PWD. + command.current_dir(cwd); + + // Configure environment variables. + let envs = env_to_strings(engine_state, stack)?; + command.env_clear(); + command.envs(envs); + + // Configure args. + command.arg(config_path); + command.args(editor_args); + + // Spawn the child process. On Unix, also put the child process to + // foreground if we're in an interactive session. + #[cfg(windows)] + let child = ForegroundChild::spawn(command)?; + #[cfg(unix)] + let child = ForegroundChild::spawn( + command, + engine_state.is_interactive, + &engine_state.pipeline_externals_state, + )?; + + // Wrap the output into a `PipelineData::ByteStream`. + let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?; + Ok(PipelineData::ByteStream( + ByteStream::child(child, call.head), + None, + )) +} diff --git a/crates/nu-command/src/env/config/config_env.rs b/crates/nu-command/src/env/config/config_env.rs index 5331d2d3be..5fdc39a903 100644 --- a/crates/nu-command/src/env/config/config_env.rs +++ b/crates/nu-command/src/env/config/config_env.rs @@ -1,7 +1,4 @@ -use nu_cmd_base::util::get_editor; -use nu_engine::{command_prelude::*, env_to_strings}; -use nu_protocol::{process::ChildProcess, ByteStream}; -use nu_system::ForegroundChild; +use nu_engine::command_prelude::*; #[derive(Clone)] pub struct ConfigEnv; @@ -81,60 +78,6 @@ impl Command for ConfigEnv { return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data()); } - // Find the editor executable. - let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; - let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( - ShellError::ExternalCommand { - label: format!("`{editor_name}` not found"), - help: "Failed to find the editor executable".into(), - span: call.head, - }, - )?; - - let Some(env_path) = engine_state.get_config_path("env-path") else { - return Err(ShellError::GenericError { - error: "Could not find $nu.env-path".into(), - msg: "Could not find $nu.env-path".into(), - span: None, - help: None, - inner: vec![], - }); - }; - let env_path = env_path.to_string_lossy().to_string(); - - // Create the command. - let mut command = std::process::Command::new(editor_executable); - - // Configure PWD. - command.current_dir(cwd); - - // Configure environment variables. - let envs = env_to_strings(engine_state, stack)?; - command.env_clear(); - command.envs(envs); - - // Configure args. - command.arg(env_path); - command.args(editor_args); - - // Spawn the child process. On Unix, also put the child process to - // foreground if we're in an interactive session. - #[cfg(windows)] - let child = ForegroundChild::spawn(command)?; - #[cfg(unix)] - let child = ForegroundChild::spawn( - command, - engine_state.is_interactive, - &engine_state.pipeline_externals_state, - )?; - - // Wrap the output into a `PipelineData::ByteStream`. - let child = ChildProcess::new(child, None, false, call.head)?; - Ok(PipelineData::ByteStream( - ByteStream::child(child, call.head), - None, - )) + super::config_::start_editor("env-path", engine_state, stack, call) } } diff --git a/crates/nu-command/src/env/config/config_nu.rs b/crates/nu-command/src/env/config/config_nu.rs index 176643a76f..75d971f36e 100644 --- a/crates/nu-command/src/env/config/config_nu.rs +++ b/crates/nu-command/src/env/config/config_nu.rs @@ -1,7 +1,4 @@ -use nu_cmd_base::util::get_editor; -use nu_engine::{command_prelude::*, env_to_strings}; -use nu_protocol::{process::ChildProcess, ByteStream}; -use nu_system::ForegroundChild; +use nu_engine::command_prelude::*; #[derive(Clone)] pub struct ConfigNu; @@ -83,60 +80,6 @@ impl Command for ConfigNu { return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data()); } - // Find the editor executable. - let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; - let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( - ShellError::ExternalCommand { - label: format!("`{editor_name}` not found"), - help: "Failed to find the editor executable".into(), - span: call.head, - }, - )?; - - let Some(config_path) = engine_state.get_config_path("config-path") else { - return Err(ShellError::GenericError { - error: "Could not find $nu.config-path".into(), - msg: "Could not find $nu.config-path".into(), - span: None, - help: None, - inner: vec![], - }); - }; - let config_path = config_path.to_string_lossy().to_string(); - - // Create the command. - let mut command = std::process::Command::new(editor_executable); - - // Configure PWD. - command.current_dir(cwd); - - // Configure environment variables. - let envs = env_to_strings(engine_state, stack)?; - command.env_clear(); - command.envs(envs); - - // Configure args. - command.arg(config_path); - command.args(editor_args); - - // Spawn the child process. On Unix, also put the child process to - // foreground if we're in an interactive session. - #[cfg(windows)] - let child = ForegroundChild::spawn(command)?; - #[cfg(unix)] - let child = ForegroundChild::spawn( - command, - engine_state.is_interactive, - &engine_state.pipeline_externals_state, - )?; - - // Wrap the output into a `PipelineData::ByteStream`. - let child = ChildProcess::new(child, None, false, call.head)?; - Ok(PipelineData::ByteStream( - ByteStream::child(child, call.head), - None, - )) + super::config_::start_editor("config-path", engine_state, stack, call) } } diff --git a/crates/nu-command/src/experimental/is_admin.rs b/crates/nu-command/src/experimental/is_admin.rs index 2dbeb0e988..37434f716e 100644 --- a/crates/nu-command/src/experimental/is_admin.rs +++ b/crates/nu-command/src/experimental/is_admin.rs @@ -103,3 +103,9 @@ fn is_root_impl() -> bool { elevated } + +#[cfg(target_arch = "wasm32")] +fn is_root_impl() -> bool { + // in wasm we don't have a user system, so technically we are never root + false +} diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 6fda754341..c4ae00f593 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -102,6 +102,7 @@ impl Command for Save { ByteStreamSource::File(source) => { stream_to_file(source, size, signals, file, span, progress)?; } + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => { fn write_or_consume_stderr( stderr: ChildPipe, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index f5e5118390..8eac0fc70b 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -37,6 +37,7 @@ mod reject; mod rename; mod reverse; mod select; +#[cfg(feature = "rand")] mod shuffle; mod skip; mod sort; @@ -95,6 +96,7 @@ pub use reject::Reject; pub use rename::Rename; pub use reverse::Reverse; pub use select::Select; +#[cfg(feature = "rand")] pub use shuffle::Shuffle; pub use skip::*; pub use sort::Sort; diff --git a/crates/nu-command/src/filters/tee.rs b/crates/nu-command/src/filters/tee.rs index a6f0027016..e643939894 100644 --- a/crates/nu-command/src/filters/tee.rs +++ b/crates/nu-command/src/filters/tee.rs @@ -1,7 +1,9 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; +#[cfg(feature = "os")] +use nu_protocol::process::ChildPipe; use nu_protocol::{ - byte_stream::copy_with_signals, engine::Closure, process::ChildPipe, report_shell_error, - ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals, + byte_stream::copy_with_signals, engine::Closure, report_shell_error, ByteStream, + ByteStreamSource, OutDest, PipelineMetadata, Signals, }; use std::{ io::{self, Read, Write}, @@ -152,6 +154,7 @@ use it in your pipeline."# metadata, )) } + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => { let stderr_thread = if use_stderr { let stderr_thread = if let Some(stderr) = child.stderr.take() { @@ -454,6 +457,7 @@ fn copy(src: impl Read, dest: impl Write, info: &StreamInfo) -> Result<(), Shell Ok(()) } +#[cfg(feature = "os")] fn copy_pipe(pipe: ChildPipe, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> { match pipe { ChildPipe::Pipe(pipe) => copy(pipe, dest, info), @@ -477,6 +481,7 @@ fn copy_on_thread( .map_err(|e| e.into_spanned(span).into()) } +#[cfg(feature = "os")] fn copy_pipe_on_thread( pipe: ChildPipe, dest: impl Write + Send + 'static, diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 55c4974325..e3b1a7f538 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "os"), allow(unused))] #![doc = include_str!("../README.md")] mod bytes; mod charting; @@ -8,6 +9,7 @@ mod default_context; mod env; mod example_test; mod experimental; +#[cfg(feature = "os")] mod filesystem; mod filters; mod formats; @@ -18,8 +20,10 @@ mod math; mod misc; mod network; mod path; +#[cfg(feature = "os")] mod platform; mod progress_bar; +#[cfg(feature = "rand")] mod random; mod removed; mod shells; @@ -27,6 +31,7 @@ mod sort_utils; #[cfg(feature = "sqlite")] mod stor; mod strings; +#[cfg(feature = "os")] mod system; mod viewers; @@ -40,6 +45,7 @@ pub use env::*; #[cfg(test)] pub use example_test::{test_examples, test_examples_with_commands}; pub use experimental::*; +#[cfg(feature = "os")] pub use filesystem::*; pub use filters::*; pub use formats::*; @@ -50,7 +56,9 @@ pub use math::*; pub use misc::*; pub use network::*; pub use path::*; +#[cfg(feature = "os")] pub use platform::*; +#[cfg(feature = "rand")] pub use random::*; pub use removed::*; pub use shells::*; @@ -58,6 +66,7 @@ pub use sort_utils::*; #[cfg(feature = "sqlite")] pub use stor::*; pub use strings::*; +#[cfg(feature = "os")] pub use system::*; pub use viewers::*; diff --git a/crates/nu-command/src/network/mod.rs b/crates/nu-command/src/network/mod.rs index 5ca523a1d3..ea46fd3dc8 100644 --- a/crates/nu-command/src/network/mod.rs +++ b/crates/nu-command/src/network/mod.rs @@ -1,8 +1,12 @@ +#[cfg(feature = "network")] mod http; +#[cfg(feature = "network")] mod port; mod url; +#[cfg(feature = "network")] pub use self::http::*; pub use self::url::*; +#[cfg(feature = "network")] pub use port::SubCommand as Port; diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 730c5ec0df..23b8259ddd 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -1,11 +1,10 @@ // use super::icons::{icon_for_file, iconify_style_ansi_to_nu}; use super::icons::icon_for_file; -use crossterm::terminal::size; use lscolors::Style; use nu_engine::{command_prelude::*, env_to_string}; use nu_protocol::Config; use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; -use nu_utils::get_ls_colors; +use nu_utils::{get_ls_colors, terminal_size}; use std::path::Path; #[derive(Clone)] @@ -192,7 +191,7 @@ fn create_grid_output( let cols = if let Some(col) = width_param { col as u16 - } else if let Ok((w, _h)) = size() { + } else if let Ok((w, _h)) = terminal_size() { w } else { 80u16 diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 43c9ed0b8d..1768590b65 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -2,7 +2,6 @@ // overall reduce the redundant calls to StyleComputer etc. // the goal is to configure it once... -use crossterm::terminal::size; use lscolors::{LsColors, Style}; use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; use nu_engine::{command_prelude::*, env_to_string}; @@ -15,7 +14,7 @@ use nu_table::{ common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, StringResult, TableOpts, TableOutput, }; -use nu_utils::get_ls_colors; +use nu_utils::{get_ls_colors, terminal_size}; use std::{ collections::VecDeque, io::{IsTerminal, Read}, @@ -30,7 +29,7 @@ const STREAM_PAGE_SIZE: usize = 1000; fn get_width_param(width_param: Option) -> usize { if let Some(col) = width_param { col as usize - } else if let Ok((w, _h)) = size() { + } else if let Ok((w, _h)) = terminal_size() { w as usize } else { 80 @@ -712,6 +711,13 @@ fn make_clickable_link( ) -> String { // uri's based on this https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + #[cfg(any( + unix, + windows, + target_os = "redox", + target_os = "wasi", + target_os = "hermit" + ))] if show_clickable_links { format!( "\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\", @@ -727,6 +733,18 @@ fn make_clickable_link( None => full_path, } } + + #[cfg(not(any( + unix, + windows, + target_os = "redox", + target_os = "wasi", + target_os = "hermit" + )))] + match link_name { + Some(link_name) => link_name.to_string(), + None => full_path, + } } struct PagingTableCreator { diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index dfe634d078..38752422da 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -14,12 +14,20 @@ bench = false workspace = true [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } nu-path = { path = "../nu-path", version = "0.100.1" } nu-glob = { path = "../nu-glob", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } log = { workspace = true } terminal_size = { workspace = true } [features] -plugin = [] \ No newline at end of file +default = ["os"] +os = [ + "nu-protocol/os", + "nu-utils/os", +] +plugin = [ + "nu-protocol/plugin", + "os", +] diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index ee0d8624d2..afcb58a90a 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -8,9 +8,9 @@ use nu_protocol::{ Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet, }, ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, - ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, - OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, - Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, + DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest, + PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, Signals, + Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, }; use nu_utils::IgnoreCaseExt; @@ -478,8 +478,9 @@ fn eval_instruction( Ok(Continue) } Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) { + #[cfg(feature = "os")] PipelineData::ByteStream(stream, _) - if matches!(stream.source(), ByteStreamSource::Child(_)) => + if matches!(stream.source(), nu_protocol::ByteStreamSource::Child(_)) => { Ok(Continue) } @@ -513,7 +514,7 @@ fn eval_instruction( span: Some(*span), })?; let is_external = if let PipelineData::ByteStream(stream, ..) = &src { - matches!(stream.source(), ByteStreamSource::Child(..)) + stream.source().is_external() } else { false }; diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index bfef11acb2..0c15a327e6 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -15,11 +15,11 @@ bench = false workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-path = { path = "../nu-path", version = "0.100.1" } nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index ac48bdfc3f..2420c8c17e 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -60,6 +60,7 @@ fn expand_tilde_with_home(path: impl AsRef, home: Option) -> Path } } +#[cfg(not(target_arch = "wasm32"))] fn fallback_home_dir(username: &str) -> PathBuf { PathBuf::from_iter([FALLBACK_USER_HOME_BASE_DIR, username]) } @@ -110,6 +111,13 @@ fn user_home_dir(username: &str) -> PathBuf { } } +#[cfg(target_arch = "wasm32")] +fn user_home_dir(_: &str) -> PathBuf { + // if WASI is used, we try to get a home dir via HOME env, otherwise we don't have a home dir + let home = std::env::var("HOME").unwrap_or_else(|_| "/".to_string()); + PathBuf::from(home) +} + /// Returns true if the shell is running inside the Termux terminal emulator /// app. #[cfg(target_os = "android")] diff --git a/crates/nu-path/src/trailing_slash.rs b/crates/nu-path/src/trailing_slash.rs index 4daf5d45ef..6a2d08ac63 100644 --- a/crates/nu-path/src/trailing_slash.rs +++ b/crates/nu-path/src/trailing_slash.rs @@ -36,6 +36,13 @@ pub fn has_trailing_slash(path: &Path) -> bool { last == Some(&b'/') } +/// `true` if the path has a trailing slash, including if it's the root directory. +#[cfg(target_arch = "wasm32")] +pub fn has_trailing_slash(path: &Path) -> bool { + // in the web paths are often just URLs, they are separated by forward slashes + path.to_str().map_or(false, |s| s.ends_with('/')) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 2c43a86c5e..0e6827bb19 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -14,8 +14,8 @@ bench = false workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.100.1" } nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-features = false } nu-utils = { path = "../nu-utils", version = "0.100.1" } diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index dfbcb7b307..34af0452f0 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -16,7 +16,7 @@ bench = false workspace = true [dependencies] -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } nu-path = { path = "../nu-path", version = "0.100.1" } nu-system = { path = "../nu-system", version = "0.100.1" } nu-derive-value = { path = "../nu-derive-value", version = "0.100.1" } @@ -31,14 +31,14 @@ fancy-regex = { workspace = true } heck = { workspace = true } indexmap = { workspace = true } lru = { workspace = true } -miette = { workspace = true, features = ["fancy-no-backtrace"] } +miette = { workspace = true, features = ["fancy-no-backtrace"]} num-format = { workspace = true } rmp-serde = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = "2.0" typetag = "0.2" -os_pipe = { workspace = true, features = ["io_safety"] } +os_pipe = { workspace = true, optional = true, features = ["io_safety"] } log = { workspace = true } [target.'cfg(unix)'.dependencies] @@ -49,8 +49,15 @@ dirs-sys = { workspace = true } windows-sys = { workspace = true } [features] +default = ["os"] +os = [ + "nu-utils/os", + "os_pipe", +] + plugin = [ "brotli", + "os", "rmp-serde", ] diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index 9e934e7f64..f187dde807 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1458,6 +1458,17 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"# #[label = "while running this code"] span: Option, }, + + #[error("OS feature is disabled: {msg}")] + #[diagnostic( + code(nu::shell::os_disabled), + help("You're probably running outside an OS like a browser, we cannot support this") + )] + DisabledOsSupport { + msg: String, + #[label = "while running this code"] + span: Option, + }, } impl ShellError { diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index e143a19819..f916bfcf64 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "os"), allow(unused))] #![doc = include_str!("../README.md")] mod alias; pub mod ast; @@ -17,6 +18,7 @@ pub mod parser_path; mod pipeline; #[cfg(feature = "plugin")] mod plugin; +#[cfg(feature = "os")] pub mod process; mod signature; pub mod span; diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index da78b9c836..2b7c93495e 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -1,8 +1,7 @@ //! Module managing the streaming of raw bytes between pipeline elements -use crate::{ - process::{ChildPipe, ChildProcess}, - ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value, -}; +#[cfg(feature = "os")] +use crate::process::{ChildPipe, ChildProcess}; +use crate::{ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value}; use serde::{Deserialize, Serialize}; #[cfg(unix)] use std::os::fd::OwnedFd; @@ -24,6 +23,7 @@ use std::{ pub enum ByteStreamSource { Read(Box), File(File), + #[cfg(feature = "os")] Child(Box), } @@ -32,6 +32,7 @@ impl ByteStreamSource { match self { ByteStreamSource::Read(read) => Some(SourceReader::Read(read)), ByteStreamSource::File(file) => Some(SourceReader::File(file)), + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => child.stdout.take().map(|stdout| match stdout { ChildPipe::Pipe(pipe) => SourceReader::File(convert_file(pipe)), ChildPipe::Tee(tee) => SourceReader::Read(tee), @@ -40,9 +41,16 @@ impl ByteStreamSource { } /// Source is a `Child` or `File`, rather than `Read`. Currently affects trimming - fn is_external(&self) -> bool { + #[cfg(feature = "os")] + pub fn is_external(&self) -> bool { matches!(self, ByteStreamSource::Child(..)) } + + #[cfg(not(feature = "os"))] + pub fn is_external(&self) -> bool { + // without os support we never have externals + false + } } impl Debug for ByteStreamSource { @@ -50,6 +58,7 @@ impl Debug for ByteStreamSource { match self { ByteStreamSource::Read(_) => f.debug_tuple("Read").field(&"..").finish(), ByteStreamSource::File(file) => f.debug_tuple("File").field(file).finish(), + #[cfg(feature = "os")] ByteStreamSource::Child(child) => f.debug_tuple("Child").field(child).finish(), } } @@ -247,6 +256,7 @@ impl ByteStream { /// /// The type is implicitly `Unknown`, as it's not typically known whether child processes will /// return text or binary. + #[cfg(feature = "os")] pub fn child(child: ChildProcess, span: Span) -> Self { Self::new( ByteStreamSource::Child(Box::new(child)), @@ -260,6 +270,7 @@ impl ByteStream { /// /// The type is implicitly `Unknown`, as it's not typically known whether stdin is text or /// binary. + #[cfg(feature = "os")] pub fn stdin(span: Span) -> Result { let stdin = os_pipe::dup_stdin().err_span(span)?; let source = ByteStreamSource::File(convert_file(stdin)); @@ -271,6 +282,14 @@ impl ByteStream { )) } + #[cfg(not(feature = "os"))] + pub fn stdin(span: Span) -> Result { + Err(ShellError::DisabledOsSupport { + msg: "Stdin is not supported".to_string(), + span: Some(span), + }) + } + /// Create a [`ByteStream`] from a generator function that writes data to the given buffer /// when called, and returns `Ok(false)` on end of stream. pub fn from_fn( @@ -432,6 +451,7 @@ impl ByteStream { match self.stream { ByteStreamSource::Read(..) => Err(self), ByteStreamSource::File(file) => Ok(file.into()), + #[cfg(feature = "os")] ByteStreamSource::Child(child) => { if let ChildProcess { stdout: Some(ChildPipe::Pipe(stdout)), @@ -453,6 +473,7 @@ impl ByteStream { /// /// This will only succeed if the [`ByteStreamSource`] of the [`ByteStream`] is [`Child`](ByteStreamSource::Child). /// All other cases return an `Err` with the original [`ByteStream`] in it. + #[cfg(feature = "os")] pub fn into_child(self) -> Result { if let ByteStreamSource::Child(child) = self.stream { Ok(*child) @@ -477,6 +498,7 @@ impl ByteStream { file.read_to_end(&mut buf).err_span(self.span)?; Ok(buf) } + #[cfg(feature = "os")] ByteStreamSource::Child(child) => child.into_bytes(), } } @@ -551,6 +573,7 @@ impl ByteStream { Ok(()) } ByteStreamSource::File(_) => Ok(()), + #[cfg(feature = "os")] ByteStreamSource::Child(child) => child.wait(), } } @@ -575,6 +598,7 @@ impl ByteStream { ByteStreamSource::File(file) => { copy_with_signals(file, dest, span, signals)?; } + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => { // All `OutDest`s except `OutDest::PipeSeparate` will cause `stderr` to be `None`. // Only `save`, `tee`, and `complete` set the stderr `OutDest` to `OutDest::PipeSeparate`, diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index a6c48cac3c..aa39d215e0 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -9,8 +9,8 @@ version = "0.100.1" [dependencies] nu-parser = { version = "0.100.1", path = "../nu-parser" } -nu-protocol = { version = "0.100.1", path = "../nu-protocol" } -nu-engine = { version = "0.100.1", path = "../nu-engine" } +nu-protocol = { version = "0.100.1", path = "../nu-protocol", default-features = false } +nu-engine = { version = "0.100.1", path = "../nu-engine", default-features = false } miette = { workspace = true, features = ["fancy-no-backtrace"] } log = "0.4" diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index c5ddb41f52..ec32249de5 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -14,9 +14,9 @@ bench = false workspace = true [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-ansi-term = { workspace = true } fancy-regex = { workspace = true } diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index a9fea5e78b..9ef7414b73 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -14,6 +14,6 @@ bench = false workspace = true [dependencies] -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index 4b590e6831..309f5f3139 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -17,6 +17,7 @@ bench = false bench = false [dependencies] +crossterm = { workspace = true, optional = true } fancy-regex = { workspace = true } lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] } log = { workspace = true } @@ -26,6 +27,10 @@ serde = { workspace = true } sys-locale = "0.3" unicase = "2.8.0" +[features] +default = ["os"] +os = ["crossterm"] + [target.'cfg(windows)'.dependencies] crossterm_winapi = "0.9" diff --git a/crates/nu-utils/src/filesystem.rs b/crates/nu-utils/src/filesystem.rs index 588f0fccff..4ea89c4e05 100644 --- a/crates/nu-utils/src/filesystem.rs +++ b/crates/nu-utils/src/filesystem.rs @@ -1,3 +1,4 @@ +#[cfg(any(windows, unix))] use std::path::Path; #[cfg(unix)] use { diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index 413b5f8893..a22c2e4537 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -12,7 +12,7 @@ pub use locale::get_system_locale; pub use utils::{ enable_vt_processing, get_default_config, get_default_env, get_ls_colors, get_sample_config, get_sample_env, get_scaffold_config, get_scaffold_env, stderr_write_all_and_flush, - stdout_write_all_and_flush, + stdout_write_all_and_flush, terminal_size, }; pub use casing::IgnoreCaseExt; diff --git a/crates/nu-utils/src/utils.rs b/crates/nu-utils/src/utils.rs index 798e542be5..74e5277ec4 100644 --- a/crates/nu-utils/src/utils.rs +++ b/crates/nu-utils/src/utils.rs @@ -1,7 +1,7 @@ #[cfg(windows)] use crossterm_winapi::{ConsoleMode, Handle}; use lscolors::LsColors; -use std::io::{Result, Write}; +use std::io::{self, Result, Write}; pub fn enable_vt_processing() -> Result<()> { #[cfg(windows)] @@ -474,3 +474,17 @@ macro_rules! perf { } }; } + +/// Returns the terminal size (columns, rows). +/// +/// This utility variant allows getting a fallback value when compiling for wasm32 without having +/// to rearrange other bits of the codebase. +/// +/// See [`crossterm::terminal::size`]. +pub fn terminal_size() -> io::Result<(u16, u16)> { + #[cfg(feature = "os")] + return crossterm::terminal::size(); + + #[cfg(not(feature = "os"))] + return Err(io::Error::from(io::ErrorKind::Unsupported)); +} diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml index d6e2a4f897..35004e1979 100644 --- a/crates/nu_plugin_custom_values/Cargo.toml +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -10,7 +10,7 @@ name = "nu_plugin_custom_values" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.100.1" } +nu-plugin = { path = "../nu-plugin", version = "0.100.1", features = [] } nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] } serde = { workspace = true } typetag = "0.2" diff --git a/crates/nuon/Cargo.toml b/crates/nuon/Cargo.toml index 4ba31b2a4a..7399fb3e78 100644 --- a/crates/nuon/Cargo.toml +++ b/crates/nuon/Cargo.toml @@ -11,9 +11,9 @@ version = "0.100.1" [dependencies] nu-parser = { path = "../nu-parser", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } [dev-dependencies] chrono = { workspace = true } diff --git a/toolkit.nu b/toolkit.nu index b8ba3ddb80..d848dce18e 100644 --- a/toolkit.nu +++ b/toolkit.nu @@ -363,6 +363,40 @@ export def build [ } } +# build crates for wasm +export def "build wasm" [] { + ^rustup target add wasm32-unknown-unknown + + # these crates should compile for wasm + let compatible_crates = [ + "nu-cmd-base", + "nu-cmd-extra", + "nu-cmd-lang", + "nu-color-config", + "nu-command", + "nu-derive-value", + "nu-engine", + "nu-glob", + "nu-json", + "nu-parser", + "nu-path", + "nu-pretty-hex", + "nu-protocol", + "nu-std", + "nu-system", + "nu-table", + "nu-term-grid", + "nu-utils", + "nuon" + ] + + for crate in $compatible_crates { + print $'(char nl)Building ($crate) for wasm' + print '----------------------------' + ^cargo build -p $crate --target wasm32-unknown-unknown --no-default-features + } +} + def "nu-complete list features" [] { open Cargo.toml | get features | transpose feature dependencies | get feature }