cargo_hyperlight/
command.rs

1use std::collections::{BTreeMap, HashMap};
2use std::convert::Infallible;
3use std::ffi::{OsStr, OsString, c_char};
4use std::fmt::Debug;
5use std::path::{Path, PathBuf};
6use std::process::Command as StdCommand;
7use std::{env, iter};
8
9use anyhow::{Context, Result};
10use os_str_bytes::OsStrBytesExt;
11
12use crate::CargoCommandExt;
13use crate::cargo_cmd::{CargoBinary, CargoCmd as _, find_cargo, merge_env};
14use crate::cli::{Args, Warning};
15
16/// A process builder for cargo commands, providing a similar API to `std::process::Command`.
17///
18/// `Command` is a wrapper around `std::process::Command` specifically designed for
19/// executing cargo commands targeting [hyperlight](https://github.com/hyperlight-dev/hyperlight)
20/// guest code.
21/// Before executing the desired command, `Command` takes care of setting up the
22/// appropriate environment. It:
23/// * creates a custom rust target for hyperlight guest code
24/// * creates a sysroot with Rust's libs core and alloc
25/// * finds the appropriate compiler and archiver for any C dependencies
26/// * sets up necessary environment variables for `cc-rs` and `bindgen` to work correctly.
27///
28/// # Examples
29///
30/// Basic usage:
31///
32/// ```rust,no_run
33/// use cargo_hyperlight::cargo;
34///
35/// let mut command = cargo().unwrap();
36/// command.arg("build").arg("--release");
37/// command.exec(); // This will replace the current process
38/// ```
39///
40/// Setting environment variables and working directory:
41///
42/// ```rust
43/// use cargo_hyperlight::cargo;
44///
45/// let mut command = cargo().unwrap();
46/// command
47///     .current_dir("/path/to/project")
48///     .env("CARGO_TARGET_DIR", "/custom/target")
49///     .args(["build", "--release"]);
50/// ```
51#[derive(Clone)]
52pub struct Command {
53    cargo: CargoBinary,
54    /// Arguments to pass to the cargo program
55    args: Vec<OsString>,
56    /// Environment variable mappings to set for the child process
57    inherit_envs: bool,
58    inherit_cargo_envs: bool,
59    envs: BTreeMap<OsString, Option<OsString>>,
60    // Working directory for the child process
61    current_dir: Option<PathBuf>,
62}
63
64impl Debug for Command {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        let args = self.build_args_infallible();
67        let mut cmd = self.command();
68        cmd.populate_from_args(&args);
69
70        write!(f, "env ")?;
71        if let Some(current_dir) = &self.current_dir {
72            write!(f, "-C {current_dir:?} ")?;
73        }
74        if !self.inherit_envs {
75            write!(f, "-i ")?;
76        }
77        for (k, v) in cmd.get_envs() {
78            if v.is_none() {
79                write!(f, "-u {} ", k.to_string_lossy())?;
80            }
81        }
82        for (k, v) in cmd.get_envs() {
83            if let Some(v) = v {
84                write!(f, "{}={:?} ", k.to_string_lossy(), v)?;
85            }
86        }
87        write!(f, "{:?} ", self.get_program())?;
88        for arg in &self.args {
89            write!(f, "{arg:?} ")?;
90        }
91        writeln!(f)
92    }
93}
94
95impl Command {
96    /// Constructs a new `Command` for launching the cargo program.
97    ///
98    /// The value of the `CARGO` environment variable is used if it is set; otherwise, the
99    /// default `cargo` from the system PATH is used.
100    /// If `RUSTUP_TOOLCHAIN` is set in the environment, it is also propagated to the
101    /// child process to ensure correct functioning of the rustup wrappers.
102    ///
103    /// The default configuration is:
104    /// - No arguments to the program
105    /// - Inherits the current process's environment
106    /// - Inherits the current process's working directory
107    ///
108    /// # Errors
109    ///
110    /// This function will return an error if:
111    /// - If the `CARGO` environment variable is set but it specifies an invalid path
112    /// - If the `CARGO` environment variable is not set and the `cargo` program cannot be found in the system PATH
113    ///
114    /// # Examples
115    ///
116    /// Basic usage:
117    ///
118    /// ```rust
119    /// use cargo_hyperlight::cargo;
120    ///
121    /// let command = cargo().unwrap();
122    /// ```
123    pub(crate) fn new() -> Result<Self> {
124        let cargo = find_cargo()?;
125        Ok(Self {
126            cargo,
127            args: Vec::new(),
128            envs: BTreeMap::new(),
129            inherit_envs: true,
130            inherit_cargo_envs: true,
131            current_dir: None,
132        })
133    }
134
135    /// Adds an argument to pass to the cargo program.
136    ///
137    /// Only one argument can be passed per use. So instead of:
138    ///
139    /// ```no_run
140    /// # let mut command = cargo_hyperlight::cargo().unwrap();
141    /// command.arg("--features some_feature");
142    /// ```
143    ///
144    /// usage would be:
145    ///
146    /// ```no_run
147    /// # let mut command = cargo_hyperlight::cargo().unwrap();
148    /// command.arg("--features").arg("some_feature");
149    /// ```
150    ///
151    /// To pass multiple arguments see [`args`].
152    ///
153    /// [`args`]: Command::args
154    ///
155    /// Note that the argument is not shell-escaped, so if you pass an argument like
156    /// `"hello world"`, it will be passed as a single argument with the literal
157    /// `hello world`, not as two arguments `hello` and `world`.
158    ///
159    /// # Examples
160    ///
161    /// Basic usage:
162    ///
163    /// ```no_run
164    /// use cargo_hyperlight::cargo;
165    ///
166    /// cargo()
167    ///     .unwrap()
168    ///     .arg("build")
169    ///     .arg("--release")
170    ///     .exec();
171    /// ```
172    pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
173        self.args.push(arg.as_ref().to_os_string());
174        self
175    }
176
177    /// Adds multiple arguments to pass to the cargo program.
178    ///
179    /// To pass a single argument see [`arg`].
180    ///
181    /// [`arg`]: Command::arg
182    ///
183    /// Note that the arguments are not shell-escaped, so if you pass an argument
184    /// like `"hello world"`, it will be passed as a single argument with the
185    /// literal `hello world`, not as two arguments `hello` and `world`.
186    ///
187    /// # Examples
188    ///
189    /// Basic usage:
190    ///
191    /// ```no_run
192    /// use cargo_hyperlight::cargo;
193    ///
194    /// cargo()
195    ///     .unwrap()
196    ///     .args(["build", "--release"])
197    ///     .exec();
198    /// ```
199    pub fn args(&mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> &mut Self {
200        for arg in args {
201            self.arg(arg);
202        }
203        self
204    }
205
206    /// Sets the working directory for the child process.
207    ///
208    /// # Examples
209    ///
210    /// Basic usage:
211    ///
212    /// ```no_run
213    /// use cargo_hyperlight::cargo;
214    ///
215    /// cargo()
216    ///     .unwrap()
217    ///     .current_dir("path/to/project")
218    ///     .arg("build")
219    ///     .exec();
220    /// ```
221    ///
222    /// [`canonicalize`]: std::fs::canonicalize
223    pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
224        self.current_dir = Some(dir.as_ref().to_path_buf());
225        self
226    }
227
228    /// Inserts or updates an explicit environment variable mapping.
229    ///
230    /// This method allows you to add an environment variable mapping to the spawned process
231    /// or overwrite a variable if it already exists.
232    ///
233    /// Child processes will inherit environment variables from their parent process by
234    /// default. Environment variables explicitly set using [`env`] take precedence
235    /// over inherited variables. You can disable environment variable inheritance entirely
236    /// using [`env_clear`] or for a single key using [`env_remove`].
237    ///
238    /// Note that environment variable names are case-insensitive (but
239    /// case-preserving) on Windows and case-sensitive on all other platforms.
240    ///
241    /// # Examples
242    ///
243    /// Basic usage:
244    ///
245    /// ```no_run
246    /// use cargo_hyperlight::cargo;
247    ///
248    /// cargo()
249    ///     .unwrap()
250    ///     .env("CARGO_TARGET_DIR", "/path/to/target")
251    ///     .arg("build")
252    ///     .exec();
253    /// ```
254    ///
255    /// [`env`]: Command::env
256    /// [`env_clear`]: Command::env_clear
257    /// [`env_remove`]: Command::env_remove
258    pub fn env(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
259        self.envs
260            .insert(key.as_ref().to_owned(), Some(value.as_ref().to_owned()));
261        self
262    }
263
264    /// Clears all environment variables that will be set for the child process.
265    ///
266    /// This method will remove all environment variables from the child process,
267    /// including those that would normally be inherited from the parent process.
268    /// Environment variables can be added back individually using [`env`].
269    ///
270    /// If `RUSTUP_TOOLCHAIN` was set in the parent process, it will be preserved.
271    ///
272    /// # Examples
273    ///
274    /// Basic usage:
275    ///
276    /// ```no_run
277    /// use cargo_hyperlight::cargo;
278    ///
279    /// cargo()
280    ///     .unwrap()
281    ///     .env_clear()
282    ///     .env("CARGO_TARGET_DIR", "/path/to/target")
283    ///     .arg("build")
284    ///     .exec();
285    /// ```
286    ///
287    /// [`env`]: Command::env
288    pub fn env_clear(&mut self) -> &mut Self {
289        self.inherit_envs = false;
290        self.envs.clear();
291        self
292    }
293
294    /// Clears all `CARGO_` environment variables that will be set for the child process,
295    /// except for `CARGO_HOME`.
296    ///
297    /// This method will remove all environment variables starting with `CARGO_`
298    /// from the child process, including those that would normally be inherited
299    /// from the parent process, except for `CARGO_HOME`. Other environment variables
300    /// will remain unaffected. Environment variables can be added back individually
301    /// using [`env`].
302    ///
303    /// This is particularly useful when using cargo-hyperlight from a build script
304    /// or other cargo-invoked context where `CARGO_` variables may change the behavior
305    /// of the cargo command being executed.
306    ///
307    /// # Examples
308    ///
309    /// Basic usage:
310    ///
311    /// ```no_run
312    /// use cargo_hyperlight::cargo;
313    ///
314    /// cargo()
315    ///     .unwrap()
316    ///     .env_clear_cargo()
317    ///     .env("CARGO_TARGET_DIR", "/path/to/target")
318    ///     .arg("build")
319    ///     .exec();
320    /// ```
321    ///
322    /// [`env`]: Command::env
323    pub fn env_clear_cargo(&mut self) -> &mut Self {
324        self.inherit_cargo_envs = false;
325        self.envs.retain(|k, _| !is_cargo_env(k));
326        self
327    }
328
329    #[doc(hidden)]
330    #[deprecated(note = "use `env_clear_cargo` instead")]
331    pub fn env_clear_cargo_vars(&mut self) -> &mut Self {
332        self.env_clear_cargo()
333    }
334
335    /// Removes an explicitly set environment variable and prevents inheriting
336    /// it from a parent process.
337    ///
338    /// This method will ensure that the specified environment variable is not
339    /// present in the spawned process's environment, even if it was present
340    /// in the parent process. This serves to "unset" environment variables.
341    ///
342    /// Note that environment variable names are case-insensitive (but
343    /// case-preserving) on Windows and case-sensitive on all other platforms.
344    ///
345    /// # Examples
346    ///
347    /// Basic usage:
348    ///
349    /// ```no_run
350    /// use cargo_hyperlight::cargo;
351    ///
352    /// cargo()
353    ///     .unwrap()
354    ///     .env_remove("CARGO_TARGET_DIR")
355    ///     .arg("build")
356    ///     .exec();
357    /// ```
358    pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
359        self.envs.insert(key.as_ref().to_owned(), None);
360        self
361    }
362
363    /// Inserts or updates multiple explicit environment variable mappings.
364    ///
365    /// This method allows you to add multiple environment variable mappings
366    /// to the spawned process or overwrite variables if they already exist.
367    /// Environment variables can be passed as a `HashMap` or any other type
368    /// implementing `IntoIterator` with the appropriate item type.
369    ///
370    /// Child processes will inherit environment variables from their parent process by
371    /// default. Environment variables explicitly set using [`env`] take precedence
372    /// over inherited variables. You can disable environment variable inheritance entirely
373    /// using [`env_clear`] or for a single key using [`env_remove`].
374    ///
375    /// Note that environment variable names are case-insensitive (but
376    /// case-preserving) on Windows and case-sensitive on all other platforms.
377    ///
378    /// # Examples
379    ///
380    /// Basic usage:
381    ///
382    /// ```no_run
383    /// use std::collections::HashMap;
384    /// use cargo_hyperlight::cargo;
385    ///
386    /// let mut envs = HashMap::new();
387    /// envs.insert("CARGO_TARGET_DIR", "/path/to/target");
388    /// envs.insert("CARGO_HOME", "/path/to/.cargo");
389    ///
390    /// cargo()
391    ///     .unwrap()
392    ///     .envs(&envs)
393    ///     .arg("build")
394    ///     .exec();
395    /// ```
396    ///
397    /// ```no_run
398    /// use cargo_hyperlight::cargo;
399    ///
400    /// cargo()
401    ///     .unwrap()
402    ///     .envs([
403    ///         ("CARGO_TARGET_DIR", "/path/to/target"),
404    ///         ("CARGO_HOME", "/path/to/.cargo"),
405    ///     ])
406    ///     .arg("build")
407    ///     .exec();
408    /// ```
409    ///
410    /// [`env`]: Command::env
411    /// [`env_clear`]: Command::env_clear
412    /// [`env_remove`]: Command::env_remove
413    pub fn envs(
414        &mut self,
415        envs: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
416    ) -> &mut Self {
417        for (k, v) in envs {
418            self.env(k, v);
419        }
420        self
421    }
422
423    /// Returns an iterator over the arguments that will be passed to the cargo program.
424    ///
425    /// This does not include the program name itself (which can be retrieved with
426    /// [`get_program`]).
427    ///
428    /// # Examples
429    ///
430    /// ```no_run
431    /// use cargo_hyperlight::cargo;
432    ///
433    /// let mut command = cargo().unwrap();
434    /// command.arg("build").arg("--release");
435    ///
436    /// let args: Vec<&std::ffi::OsStr> = command.get_args().collect();
437    /// assert_eq!(args, &["build", "--release"]);
438    /// ```
439    ///
440    /// [`get_program`]: Command::get_program
441    pub fn get_args(&'_ self) -> impl Iterator<Item = &OsStr> {
442        self.args.iter().map(|s| s.as_os_str())
443    }
444
445    /// Returns the working directory for the child process.
446    ///
447    /// This returns `None` if the working directory will not be changed from
448    /// the current directory of the parent process.
449    ///
450    /// # Examples
451    ///
452    /// ```no_run
453    /// use std::path::Path;
454    /// use cargo_hyperlight::cargo;
455    ///
456    /// let mut command = cargo().unwrap();
457    /// assert_eq!(command.get_current_dir(), None);
458    ///
459    /// command.current_dir("/tmp");
460    /// assert_eq!(command.get_current_dir(), Some(Path::new("/tmp")));
461    /// ```
462    pub fn get_current_dir(&self) -> Option<&Path> {
463        self.current_dir.as_deref()
464    }
465
466    /// Returns an iterator over the environment mappings that will be set for the child process.
467    ///
468    /// Environment variables explicitly set or unset via [`env`], [`envs`], and
469    /// [`env_remove`] can be retrieved with this method.
470    ///
471    /// Note that this output does not include environment variables inherited from the
472    /// parent process.
473    ///
474    /// Each element is a tuple key/value where `None` means the variable is explicitly
475    /// unset in the child process environment.
476    ///
477    /// # Examples
478    ///
479    /// ```no_run
480    /// use std::ffi::OsStr;
481    /// use cargo_hyperlight::cargo;
482    ///
483    /// let mut command = cargo().unwrap();
484    /// command.env("CARGO_HOME", "/path/to/.cargo");
485    /// command.env_remove("CARGO_TARGET_DIR");
486    ///
487    /// for (key, value) in command.get_envs() {
488    ///     println!("{key:?} => {value:?}");
489    /// }
490    /// ```
491    ///
492    /// [`env`]: Command::env
493    /// [`envs`]: Command::envs
494    /// [`env_remove`]: Command::env_remove
495    pub fn get_envs(&'_ self) -> impl Iterator<Item = (&OsStr, Option<&OsStr>)> {
496        self.envs.iter().map(|(k, v)| (k.as_os_str(), v.as_deref()))
497    }
498
499    /// Returns the base environment variables for the command.
500    ///
501    /// This method returns the environment variables that will be inherited
502    /// from the current process, taking into account whether [`env_clear`] has been called.
503    ///
504    /// [`env_clear`]: Command::env_clear
505    fn base_env(&self) -> impl Iterator<Item = (OsString, OsString)> {
506        env::vars_os().filter(|(k, _)| {
507            if !self.inherit_envs {
508                false
509            } else if !self.inherit_cargo_envs {
510                !is_cargo_env(k)
511            } else {
512                true
513            }
514        })
515    }
516
517    fn resolve_env(&self) -> HashMap<OsString, OsString> {
518        merge_env(self.base_env(), self.get_envs())
519    }
520
521    fn command(&self) -> StdCommand {
522        let mut command = self.cargo.command();
523        command.args(self.get_args());
524        if let Some(cwd) = &self.current_dir {
525            command.current_dir(cwd);
526        }
527        if !self.inherit_envs {
528            command.env_clear();
529        }
530        if !self.inherit_cargo_envs {
531            for (k, _) in env::vars_os() {
532                if is_cargo_env(&k) {
533                    command.env_remove(k);
534                }
535            }
536        }
537        if let Some(rustup_toolchain) = &self.cargo.rustup_toolchain {
538            command.env("RUSTUP_TOOLCHAIN", rustup_toolchain);
539        }
540        for (k, v) in self.get_envs() {
541            match v {
542                Some(v) => command.env(k, v),
543                None => command.env_remove(k),
544            };
545        }
546        command
547    }
548
549    /// Returns the path to the cargo program that will be executed.
550    ///
551    /// # Examples
552    ///
553    /// ```no_run
554    /// use cargo_hyperlight::cargo;
555    ///
556    /// let command = cargo().unwrap();
557    /// println!("Program: {:?}", command.get_program());
558    /// ```
559    pub fn get_program(&self) -> &OsStr {
560        self.cargo.path.as_os_str()
561    }
562
563    fn build_args(&self) -> Args {
564        // parse the arguments and environment variables
565        match Args::parse(
566            self.get_args(),
567            self.resolve_env(),
568            self.get_current_dir(),
569            Warning::WARN,
570        ) {
571            Ok(args) => args,
572        }
573    }
574
575    fn build_args_infallible(&self) -> Args {
576        match Args::parse(
577            self.get_args(),
578            self.resolve_env(),
579            self.get_current_dir(),
580            Warning::IGNORE,
581        ) {
582            Ok(args) => args,
583            Err(err) => {
584                eprintln!("Failed to parse arguments: {err}");
585                std::process::exit(1);
586            }
587        }
588    }
589
590    /// Executes a cargo command as a child process, waiting for it to finish and
591    /// collecting its exit status.
592    ///
593    /// The process stdin, stdout and stderr are inherited from the parent.
594    ///
595    /// # Examples
596    ///
597    /// Basic usage:
598    ///
599    /// ```no_run
600    /// use cargo_hyperlight::cargo;
601    ///
602    /// let result = cargo()
603    ///     .unwrap()
604    ///     .arg("build")
605    ///     .status();
606    ///
607    /// match result {
608    ///     Ok(()) => println!("Cargo command succeeded"),
609    ///     Err(e) => println!("Cargo command failed: {}", e),
610    /// }
611    /// ```
612    ///
613    /// # Errors
614    ///
615    /// This method will return an error if:
616    /// - The sysroot preparation fails
617    /// - The cargo process could not be spawned
618    /// - The cargo process returned a non-zero exit status
619    pub fn status(&self) -> anyhow::Result<()> {
620        let args = self.build_args();
621
622        args.prepare_sysroot()
623            .context("Failed to prepare sysroot")?;
624
625        self.command()
626            .populate_from_args(&args)
627            .checked_status()
628            .context("Failed to execute cargo")?;
629        Ok(())
630    }
631
632    /// Executes the cargo command, replacing the current process.
633    ///
634    /// This function will never return on success, as it replaces the current process
635    /// with the cargo process. On error, it will print the error and exit with code 101.
636    ///
637    /// # Examples
638    ///
639    /// Basic usage:
640    ///
641    /// ```no_run
642    /// use cargo_hyperlight::cargo;
643    ///
644    /// cargo()
645    ///     .unwrap()
646    ///     .arg("build")
647    ///     .exec(); // This will never return
648    /// ```
649    ///
650    /// # Errors
651    ///
652    /// This function will exit the process with code 101 if:
653    /// - The sysroot preparation fails
654    /// - The process replacement fails
655    pub fn exec(&self) -> ! {
656        match self.exec_impl() {
657            Err(e) => {
658                eprintln!("{e:?}");
659                std::process::exit(101);
660            }
661        }
662    }
663
664    /// Internal implementation of process replacement.
665    ///
666    /// This method prepares the sysroot and then calls the low-level `exec` function
667    /// to replace the current process.
668    fn exec_impl(&self) -> anyhow::Result<Infallible> {
669        let args = self.build_args();
670
671        args.prepare_sysroot()
672            .context("Failed to prepare sysroot")?;
673
674        let mut command = self.command();
675        command.populate_from_args(&args);
676
677        if let Some(cwd) = self.get_current_dir() {
678            env::set_current_dir(cwd).context("Failed to change current directory")?;
679        }
680
681        Ok(exec(
682            command.get_program(),
683            command.get_args(),
684            command.resolve_env(self.base_env()),
685        )?)
686    }
687}
688
689/// Replaces the current process with the specified program using `execvpe`.
690///
691/// This function converts the provided arguments and environment variables into
692/// the format expected by the `execvpe` system call and then replaces the current
693/// process with the new program.
694///
695/// # Arguments
696///
697/// * `program` - The path to the program to execute
698/// * `args` - The command-line arguments to pass to the program
699/// * `envs` - The environment variables to set for the new process
700///
701/// # Returns
702///
703/// This function should never return on success. On failure, it returns an
704/// `std::io::Error` describing what went wrong.
705///
706/// # Safety
707///
708/// This function uses unsafe code to call `libc::execvpe`. The implementation
709/// carefully manages memory to ensure null-terminated strings are properly
710/// constructed for the system call.
711fn exec(
712    program: impl AsRef<OsStr>,
713    args: impl IntoIterator<Item = impl AsRef<OsStr>>,
714    envs: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
715) -> std::io::Result<Infallible> {
716    let mut env_bytes = vec![];
717    let mut env_offsets = vec![];
718    for (k, v) in envs.into_iter() {
719        env_offsets.push(env_bytes.len());
720        env_bytes.extend_from_slice(k.as_ref().as_encoded_bytes());
721        env_bytes.push(b'=');
722        env_bytes.extend_from_slice(v.as_ref().as_encoded_bytes());
723        env_bytes.push(0);
724    }
725    let env_ptrs = env_offsets
726        .into_iter()
727        .map(|offset| env_bytes[offset..].as_ptr() as *const c_char)
728        .chain(iter::once(std::ptr::null()))
729        .collect::<Vec<_>>();
730
731    let mut arg_bytes = vec![];
732    let mut arg_offsets = vec![];
733
734    arg_offsets.push(arg_bytes.len());
735    arg_bytes.extend_from_slice(program.as_ref().as_encoded_bytes());
736    arg_bytes.push(0);
737
738    for arg in args {
739        arg_offsets.push(arg_bytes.len());
740        arg_bytes.extend_from_slice(arg.as_ref().as_encoded_bytes());
741        arg_bytes.push(0);
742    }
743    let arg_ptrs = arg_offsets
744        .into_iter()
745        .map(|offset| arg_bytes[offset..].as_ptr() as *const c_char)
746        .chain(iter::once(std::ptr::null()))
747        .collect::<Vec<_>>();
748
749    unsafe { libc::execvpe(arg_ptrs[0], arg_ptrs.as_ptr(), env_ptrs.as_ptr()) };
750
751    Err(std::io::Error::last_os_error())
752}
753
754fn is_cargo_env(key: &OsStr) -> bool {
755    key != "CARGO_HOME" && key.starts_with("CARGO_")
756}