nu_cmd_lang/core_commands/
version.rs

1use std::{borrow::Cow, sync::OnceLock};
2
3use itertools::Itertools;
4use nu_engine::command_prelude::*;
5use nu_protocol::engine::StateWorkingSet;
6use shadow_rs::shadow;
7
8shadow!(build);
9
10/// Static container for the cargo features used by the `version` command.
11///
12/// This `OnceLock` holds the features from `nu`.
13/// When you build `nu_cmd_lang`, Cargo doesn't pass along the same features that `nu` itself uses.
14/// By setting this static before calling `version`, you make it show `nu`'s features instead
15/// of `nu_cmd_lang`'s.
16///
17/// Embedders can set this to any feature list they need, but in most cases you'll probably want to
18/// pass the cargo features of your host binary.
19///
20/// # How to get cargo features in your build script
21///
22/// In your binary's build script:
23/// ```rust,ignore
24/// // Re-export CARGO_CFG_FEATURE to the main binary.
25/// // It holds all the features that cargo sets for your binary as a comma-separated list.
26/// println!(
27///     "cargo:rustc-env=NU_FEATURES={}",
28///     std::env::var("CARGO_CFG_FEATURE").expect("set by cargo")
29/// );
30/// ```
31///
32/// Then, before you call `version`:
33/// ```rust,ignore
34/// // This uses static strings, but since we're using `Cow`, you can also pass owned strings.
35/// let features = env!("NU_FEATURES")
36///     .split(',')
37///     .map(Cow::Borrowed)
38///     .collect();
39///
40/// nu_cmd_lang::VERSION_NU_FEATURES
41///     .set(features)
42///     .expect("couldn't set VERSION_NU_FEATURES");
43/// ```
44pub static VERSION_NU_FEATURES: OnceLock<Vec<Cow<'static, str>>> = OnceLock::new();
45
46#[derive(Clone)]
47pub struct Version;
48
49impl Command for Version {
50    fn name(&self) -> &str {
51        "version"
52    }
53
54    fn signature(&self) -> Signature {
55        Signature::build("version")
56            .input_output_types(vec![(Type::Nothing, Type::record())])
57            .allow_variants_without_examples(true)
58            .category(Category::Core)
59    }
60
61    fn description(&self) -> &str {
62        "Display Nu version, and its build configuration."
63    }
64
65    fn is_const(&self) -> bool {
66        true
67    }
68
69    fn run(
70        &self,
71        engine_state: &EngineState,
72        _stack: &mut Stack,
73        call: &Call,
74        _input: PipelineData,
75    ) -> Result<PipelineData, ShellError> {
76        version(engine_state, call.head)
77    }
78
79    fn run_const(
80        &self,
81        working_set: &StateWorkingSet,
82        call: &Call,
83        _input: PipelineData,
84    ) -> Result<PipelineData, ShellError> {
85        version(working_set.permanent(), call.head)
86    }
87
88    fn examples(&self) -> Vec<Example> {
89        vec![Example {
90            description: "Display Nu version",
91            example: "version",
92            result: None,
93        }]
94    }
95}
96
97fn push_non_empty(record: &mut Record, name: &str, value: &str, span: Span) {
98    if !value.is_empty() {
99        record.push(name, Value::string(value, span))
100    }
101}
102
103pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, ShellError> {
104    // Pre-allocate the arrays in the worst case (17 items):
105    // - version
106    // - major
107    // - minor
108    // - patch
109    // - pre
110    // - branch
111    // - commit_hash
112    // - build_os
113    // - build_target
114    // - rust_version
115    // - rust_channel
116    // - cargo_version
117    // - build_time
118    // - build_rust_channel
119    // - allocator
120    // - features
121    // - installed_plugins
122    let mut record = Record::with_capacity(17);
123
124    record.push("version", Value::string(env!("CARGO_PKG_VERSION"), span));
125
126    push_version_numbers(&mut record, span);
127
128    push_non_empty(&mut record, "pre", build::PKG_VERSION_PRE, span);
129
130    record.push("branch", Value::string(build::BRANCH, span));
131
132    if let Some(commit_hash) = option_env!("NU_COMMIT_HASH") {
133        record.push("commit_hash", Value::string(commit_hash, span));
134    }
135
136    push_non_empty(&mut record, "build_os", build::BUILD_OS, span);
137    push_non_empty(&mut record, "build_target", build::BUILD_TARGET, span);
138    push_non_empty(&mut record, "rust_version", build::RUST_VERSION, span);
139    push_non_empty(&mut record, "rust_channel", build::RUST_CHANNEL, span);
140    push_non_empty(&mut record, "cargo_version", build::CARGO_VERSION, span);
141    push_non_empty(&mut record, "build_time", build::BUILD_TIME, span);
142    push_non_empty(
143        &mut record,
144        "build_rust_channel",
145        build::BUILD_RUST_CHANNEL,
146        span,
147    );
148
149    record.push("allocator", Value::string(global_allocator(), span));
150
151    record.push(
152        "features",
153        Value::string(
154            VERSION_NU_FEATURES
155                .get()
156                .as_ref()
157                .map(|v| v.as_slice())
158                .unwrap_or_default()
159                .iter()
160                .filter(|f| !f.starts_with("dep:"))
161                .join(", "),
162            span,
163        ),
164    );
165
166    #[cfg(not(feature = "plugin"))]
167    let _ = engine_state;
168
169    #[cfg(feature = "plugin")]
170    {
171        // Get a list of plugin names and versions if present
172        let installed_plugins = engine_state
173            .plugins()
174            .iter()
175            .map(|x| {
176                let name = x.identity().name();
177                if let Some(version) = x.metadata().and_then(|m| m.version) {
178                    format!("{name} {version}")
179                } else {
180                    name.into()
181                }
182            })
183            .collect::<Vec<_>>();
184
185        record.push(
186            "installed_plugins",
187            Value::string(installed_plugins.join(", "), span),
188        );
189    }
190
191    record.push(
192        "experimental_options",
193        Value::string(
194            nu_experimental::ALL
195                .iter()
196                .map(|option| format!("{}={}", option.identifier(), option.get()))
197                .join(", "),
198            span,
199        ),
200    );
201
202    Ok(Value::record(record, span).into_pipeline_data())
203}
204
205/// Add version numbers as integers to the given record
206fn push_version_numbers(record: &mut Record, head: Span) {
207    static VERSION_NUMBERS: OnceLock<(u8, u8, u8)> = OnceLock::new();
208
209    let &(major, minor, patch) = VERSION_NUMBERS.get_or_init(|| {
210        (
211            build::PKG_VERSION_MAJOR.parse().expect("Always set"),
212            build::PKG_VERSION_MINOR.parse().expect("Always set"),
213            build::PKG_VERSION_PATCH.parse().expect("Always set"),
214        )
215    });
216    record.push("major", Value::int(major.into(), head));
217    record.push("minor", Value::int(minor.into(), head));
218    record.push("patch", Value::int(patch.into(), head));
219}
220
221fn global_allocator() -> &'static str {
222    "standard"
223}
224
225#[cfg(test)]
226mod test {
227    #[test]
228    fn test_examples() {
229        use super::Version;
230        use crate::test_examples;
231        test_examples(Version)
232    }
233}