diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 6cedcd6552..4dc2728f89 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -901,6 +901,50 @@ jobs: --features "${{ matrix.job.features }},openssl" \ -E 'test(/^test_(md5sum|sha1sum|sha224sum|sha256sum|sha384sum|sha512sum|cksum)::/)' + test_no_diagnostics: + name: Build/No-diagnostics + needs: [ min_version, deps ] + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@nextest + - uses: Swatinem/rust-cache@v2 + - name: Run sccache-cache + id: sccache-setup + uses: mozilla-actions/sccache-action@v0.0.11 + continue-on-error: true + - name: Export sccache + if: steps.sccache-setup.outcome == 'success' + run: | + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV + # The caret diagnostics (`feat_diagnostics`) are on by default; packagers + # who want the smaller dependency tree build with them off, so that build + # must stay green. The caret snapshot tests are gated on the feature and + # compile out; everything else runs as usual against the plain messages. + - name: Test without the diagnostics feature + shell: bash + env: + RUST_BACKTRACE: "1" + run: | + cargo nextest run --hide-progress-bar --profile ci \ + --no-default-features --features feat_os_unix + # Confirm the feature actually compiles the renderer out. If ariadne is + # still in the tree, some crate re-grew a hard dependency on it. + # (`cargo tree -i` exits non-zero when the crate is absent from the graph.) + - name: Verify ariadne is compiled out + shell: bash + run: | + if cargo tree -p coreutils --no-default-features --features feat_os_unix -e normal -i ariadne 2>/dev/null; then + echo "ERROR: ariadne is still in the dependency tree without feat_diagnostics" + exit 1 + fi + echo "OK: ariadne is not in the dependency tree" + test_selinux: name: Build/SELinux needs: [ min_version, deps ] diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index d0e6275bfb..c322a09209 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -59,6 +59,9 @@ jobs: # Run host-compiled integration tests against the WASI binary. # Tests incompatible with WASI are annotated with # #[cfg_attr(wasi_runner, ignore)] in the test source files. + # The binary above is built without default features, so it has no + # caret diagnostics; the host test crate does have them, hence the + # not(wasi_runner) in the cfg guarding every `mod diagnostics`. # TODO: add integration tests for these tools as WASI support is extended: # arch b2sum cat cksum csplit date dir dircolors fmt join # ls md5sum mkdir mv nproc pathchk pr printenv ptx pwd readlink diff --git a/Cargo.toml b/Cargo.toml index 57ec326746..83155ab5a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ categories.workspace = true all-features = true [features] -default = ["feat_common_core"] +default = ["feat_common_core", "feat_diagnostics"] ## OS feature shortcodes unix = ["feat_os_unix"] windows = ["feat_os_windows"] @@ -62,6 +62,15 @@ uudoc = [ "dep:zip", ] ## features +# "feat_diagnostics" == render errors against the argument list with a caret +# when stderr is a terminal. On by default; build with `--no-default-features` +# (plus a `feat_os_*` selection) to compile the rendering — and its ariadne +# dependency — out. Utilities then keep their plain one-line messages. +# Enabling `uucore/diagnostics` is enough: cargo unifies features, so every +# utility in the build sees it. The matching `diagnostics` feature each `uu_*` +# crate declares is there for building that crate on its own, and is +# deliberately not listed here. +feat_diagnostics = ["uucore/diagnostics"] ## Optional feature for stdbuf # "feat_external_libstdbuf" == use an external libstdbuf.so for stdbuf instead of embedding it feat_external_libstdbuf = ["stdbuf/feat_external_libstdbuf"] @@ -396,6 +405,11 @@ version = "0.10.0" [workspace.dependencies] ansi-width = "0.1.0" +# The diagnostics rewrite ariadne's output by row number, so a release that +# changes the shape of a report is a breaking change here even when it is not +# one for ariadne. `uucore`'s `the_rendered_report_has_the_row_shape_the_rewriting_assumes` +# is what catches it; keep the bound tight enough that only patch releases +# arrive without one running. ariadne = "0.6.0" base64-simd = "0.8" bigdecimal = "0.4" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d7b3626a54..d2d1904888 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -73,16 +73,6 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -[[package]] -name = "ariadne" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8454c8a44ce2cb9cc7e7fae67fc6128465b343b92c6631e94beca3c8d1524ea5" -dependencies = [ - "unicode-width", - "yansi", -] - [[package]] name = "arrayref" version = "0.3.9" @@ -2057,7 +2047,6 @@ dependencies = [ name = "uucore" version = "0.10.0" dependencies = [ - "ariadne", "base64-simd", "bigdecimal", "blake2b_simd", @@ -2323,12 +2312,6 @@ dependencies = [ "either", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" version = "0.8.3" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 509616e4b1..63520511ab 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -18,18 +18,15 @@ doctest = false [dependencies] clap = { workspace = true } thiserror = { workspace = true } -uucore = { workspace = true, features = [ - "diagnostics", - "entries", - "fs", - "mode", - "perms", -] } +uucore = { workspace = true, features = ["entries", "fs", "mode", "perms"] } fluent = { workspace = true } [target.'cfg(all(unix, not(target_os = "redox")))'.dependencies] uucore = { workspace = true, features = ["safe-traversal"] } +[features] +diagnostics = ["uucore/diagnostics"] + [lints] workspace = true diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 9fd25e9514..69c07e1788 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -20,7 +20,7 @@ clap = { workspace = true } num-bigint = { workspace = true } num-traits = { workspace = true } onig = { workspace = true } -uucore = { workspace = true, features = ["diagnostics", "i18n-collator"] } +uucore = { workspace = true, features = ["i18n-collator"] } thiserror = { workspace = true } fluent = { workspace = true } @@ -28,6 +28,9 @@ fluent = { workspace = true } divan = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } +[features] +diagnostics = ["uucore/diagnostics"] + [lints] workspace = true diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index f5d78207a5..ceda3ec006 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -21,7 +21,6 @@ thiserror = { workspace = true } uucore = { workspace = true, default-features = true, features = [ "backup-control", "buf-copy", - "diagnostics", "fs", "mode", "perms", @@ -35,6 +34,7 @@ rustix = { workspace = true } selinux = { workspace = true, optional = true } [features] +diagnostics = ["uucore/diagnostics"] selinux = ["dep:selinux", "uucore/selinux"] [lints] diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index bbcd30bae3..32715028c8 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -18,18 +18,14 @@ doctest = false [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = [ - "diagnostics", - "fs", - "fsxattr", - "mode", -] } +uucore = { workspace = true, features = ["fs", "fsxattr", "mode"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["process", "fs"] } [features] +diagnostics = ["uucore/diagnostics"] selinux = ["uucore/selinux"] smack = ["uucore/smack"] diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 0f21a2c1ef..909c834c82 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -18,7 +18,7 @@ doctest = false [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["diagnostics", "fs", "mode"] } +uucore = { workspace = true, features = ["fs", "mode"] } fluent = { workspace = true } [target.'cfg(unix)'.dependencies] @@ -28,6 +28,7 @@ rustix = { workspace = true, features = ["fs", "process"] } libc = { workspace = true } [features] +diagnostics = ["uucore/diagnostics"] selinux = ["uucore/selinux"] smack = ["uucore/smack"] diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 9aebee8e25..605fa94ee0 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -19,11 +19,12 @@ doctest = false [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["diagnostics", "fs", "mode"] } +uucore = { workspace = true, features = ["fs", "mode"] } fluent = { workspace = true } nix = { workspace = true } [features] +diagnostics = ["uucore/diagnostics"] selinux = ["uucore/selinux"] smack = ["uucore/smack"] diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index ac8160cfd7..ebe165fae4 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -17,7 +17,6 @@ path = "src/numfmt.rs" [dependencies] clap = { workspace = true } uucore = { workspace = true, features = [ - "diagnostics", "parser", "ranges", "i18n-common", @@ -31,6 +30,9 @@ memchr = { workspace = true } divan = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } +[features] +diagnostics = ["uucore/diagnostics"] + [lints] workspace = true diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index 03fe4dee97..a27b4129c0 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -18,13 +18,12 @@ doctest = false [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = [ - "diagnostics", - "format", - "quoting-style", -] } +uucore = { workspace = true, features = ["format", "quoting-style"] } fluent = { workspace = true } +[features] +diagnostics = ["uucore/diagnostics"] + [lints] workspace = true diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index aae94eea7f..e1998bd7b4 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -19,6 +19,7 @@ doctest = false [features] default = ["i18n-collator"] +diagnostics = ["uucore/diagnostics"] i18n-collator = ["uucore/i18n-collator"] [dependencies] @@ -34,7 +35,6 @@ self_cell = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } uucore = { workspace = true, features = [ - "diagnostics", "fs", "parser-size", "version-cmp", diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index faf0ae96f0..946c599baf 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -20,7 +20,7 @@ clap = { workspace = true } fluent = { workspace = true } libc = { workspace = true } thiserror = { workspace = true } -uucore = { workspace = true, features = ["diagnostics", "process", "wide"] } +uucore = { workspace = true, features = ["process", "wide"] } [target.'cfg(windows)'.dependencies] windows-sys = { workspace = true, features = [ @@ -33,6 +33,9 @@ windows-sys = { workspace = true, features = [ [dev-dependencies] tempfile = { workspace = true } +[features] +diagnostics = ["uucore/diagnostics"] + [lints] workspace = true diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 623a60fdae..def15475eb 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -19,7 +19,7 @@ doctest = false [dependencies] nom = { workspace = true } clap = { workspace = true } -uucore = { workspace = true, features = ["diagnostics", "fs", "signals"] } +uucore = { workspace = true, features = ["fs", "signals"] } fluent = { workspace = true } bytecount = { workspace = true, features = ["runtime-dispatch-simd"] } @@ -28,6 +28,9 @@ divan = { workspace = true } rustix = { workspace = true, features = ["stdio", "fs"] } uucore = { workspace = true, features = ["benchmark"] } +[features] +diagnostics = ["uucore/diagnostics"] + [lints] workspace = true diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index b52c89d62b..1ccee6a597 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -175,7 +175,7 @@ i18n-datetime = [ "jiff-icu", "jiff", ] -mode = ["diagnostics", "libc"] +mode = ["libc"] perms = ["entries", "libc", "walkdir"] buf-copy = [] parser-num = ["extendedbigdecimal", "num-traits"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 4ec60cc8de..883883efc6 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -19,6 +19,11 @@ pub mod checksum; pub mod colors; #[cfg(feature = "diagnostics")] pub mod diagnostics; +// Without the feature, a no-op stand-in keeps the API — and its callers — +// compiling; they all fall back to their plain one-line messages. +#[cfg(not(feature = "diagnostics"))] +#[path = "features/diagnostics_stub.rs"] +pub mod diagnostics; #[cfg(feature = "encoding")] pub mod encoding; #[cfg(feature = "extendedbigdecimal")] diff --git a/src/uucore/src/lib/features/diagnostics_stub.rs b/src/uucore/src/lib/features/diagnostics_stub.rs new file mode 100644 index 0000000000..49da229461 --- /dev/null +++ b/src/uucore/src/lib/features/diagnostics_stub.rs @@ -0,0 +1,94 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! No-op stand-in for the `diagnostics` module, compiled when the +//! `diagnostics` cargo feature is off. +//! +//! It mirrors the real API so callers need no `cfg` of their own: +//! [`enabled`] is always `false`, the locators find nothing and the render +//! methods render nothing, so every caller falls back to the plain one-line +//! message it would print anywhere but a terminal. + +use std::ffi::{OsStr, OsString}; +use std::ops::Range; + +/// Always `false`: rendering is compiled out. +pub fn enabled() -> bool { + false +} + +/// Always `None`: rendering is compiled out. +pub fn capture(_args: &[OsString]) -> Option> { + None +} + +/// Always `None`: rendering is compiled out. +pub fn operands(_args: &[OsString]) -> Option> { + None +} + +/// A snapshot of nothing: it finds nothing and renders nothing. +/// +/// Deliberately derives nothing the real `Snapshot` does not, so that what a +/// caller may do with one does not depend on whether the feature is on. +pub struct Snapshot; + +#[allow(clippy::unused_self)] +impl Snapshot { + pub fn new>(_args: &[S]) -> Self { + Self + } + + pub fn with_program>(_args: &[S]) -> Self { + Self + } + + pub fn from_bytes>(_args: &[S]) -> Self { + Self + } + + pub fn is_empty(&self) -> bool { + true + } + + pub fn index_of(&self, _arg: &OsStr) -> Option { + None + } + + pub fn index_of_value( + &self, + _operand: &str, + _short: Option, + _long: Option<&str>, + ) -> Option { + None + } + + pub fn index_of_positional(&self, _n: usize) -> Option { + None + } + + pub fn render( + &self, + _index: usize, + _message: &str, + _label: Option<&str>, + _help: Option<&str>, + ) -> bool { + false + } + + pub fn render_inside_at( + &self, + _index: usize, + _operand: &str, + _range: Range, + _message: &str, + _label: Option<&str>, + _help: Option<&str>, + ) -> bool { + false + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 048d6a0ca0..3bd08333a1 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -45,7 +45,6 @@ pub use crate::features::char_width; pub use crate::features::checksum; #[cfg(feature = "colors")] pub use crate::features::colors; -#[cfg(feature = "diagnostics")] pub use crate::features::diagnostics; #[cfg(feature = "encoding")] pub use crate::features::encoding; diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 6b4f34b27e..ca13fdef47 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -1691,6 +1691,7 @@ fn test_chmod_symlink_two_links_same_dir() { // cSpell:enable } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; #[cfg(unix)] diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index d14e12384d..182bbf28e8 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -2040,6 +2040,7 @@ fn test_emoji_operations() { .stdout_only("1\n"); } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; #[cfg(unix)] diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 0e471f0563..107c54c168 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -3073,6 +3073,7 @@ fn test_install_backup_custom_suffix_refuses() { // The mode is only parsed where a mode means something. #[cfg(unix)] +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; #[test] diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index 42bf921953..e335a3f056 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -1093,6 +1093,7 @@ fn test_mkdir_inside_inexistent_dir() { // The mode is only parsed where a mode means something. #[cfg(unix)] +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; #[test] diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index ecbfe4fbee..a4f5f61928 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -252,6 +252,7 @@ fn test_mkfifo_permission_unchanged_when_failed() { // The mode is only parsed where a mode means something. #[cfg(unix)] +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; #[test] diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index b0d9f23c89..647cc19e0f 100644 --- a/tests/by-util/test_mknod.rs +++ b/tests/by-util/test_mknod.rs @@ -273,6 +273,7 @@ fn test_mknod_selinux_invalid_cleanup() { // The mode is only parsed where a mode means something. #[cfg(unix)] +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; #[test] diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 31a8f9e987..5e5a35bc38 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -1698,6 +1698,7 @@ fn test_header_detached() { .stdout_is("1\n2\n"); } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 7662b781d5..dab7dd547e 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1602,6 +1602,7 @@ fn test_empty_output_succeeds_on_full_device() { .no_output(); } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { #[cfg(unix)] use super::*; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index f8df337e33..f12850f28e 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -3190,6 +3190,7 @@ fn test_sort_locale_punctuation() { } } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 556c543b7a..044760c0d4 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -1192,6 +1192,7 @@ fn test_unary_op_as_literal_in_three_arg_form() { new_ucmd!().args(&["-f", "=", "a", "-o", "b"]).succeeds(); } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*; diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 6fad3ca116..c9bb152792 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1686,6 +1686,7 @@ fn test_stdin_is_socket() { .stdout_is(";;"); } +#[cfg(all(feature = "feat_diagnostics", not(wasi_runner)))] mod diagnostics { use super::*;