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 Cargo build-context environment variables from the child process.
295 ///
296 /// This method removes the `CARGO_` environment variables that Cargo sets
297 /// during build script execution and crate compilation (e.g. `CARGO_PKG_*`,
298 /// `CARGO_MANIFEST_*`, `CARGO_CFG_*`, `CARGO_FEATURE_*`, etc.).
299 ///
300 /// User configuration variables are preserved, including:
301 /// - `CARGO_HOME` — Cargo home directory
302 /// - `CARGO_REGISTRIES_*` — Private registry index URLs, tokens, and credential providers
303 /// - `CARGO_REGISTRY_*` — Default registry and crates.io credentials
304 /// - `CARGO_HTTP_*` — HTTP/TLS proxy and timeout settings
305 /// - `CARGO_NET_*` — Network configuration (retry, offline, git-fetch-with-cli)
306 /// - `CARGO_ALIAS_*` — Command aliases
307 /// - `CARGO_TERM_*` — Terminal output settings
308 ///
309 /// Other environment variables will remain unaffected. Environment variables
310 /// can be added back individually using [`env`].
311 ///
312 /// This is particularly useful when using cargo-hyperlight from a build script
313 /// or other cargo-invoked context where `CARGO_` variables may change the behavior
314 /// of the cargo command being executed.
315 ///
316 /// # Examples
317 ///
318 /// Basic usage:
319 ///
320 /// ```no_run
321 /// use cargo_hyperlight::cargo;
322 ///
323 /// cargo()
324 /// .unwrap()
325 /// .env_clear_cargo()
326 /// .env("CARGO_TARGET_DIR", "/path/to/target")
327 /// .arg("build")
328 /// .exec();
329 /// ```
330 ///
331 /// [`env`]: Command::env
332 pub fn env_clear_cargo(&mut self) -> &mut Self {
333 self.inherit_cargo_envs = false;
334 self.envs.retain(|k, _| should_preserve_cargo_env(k));
335 self
336 }
337
338 #[doc(hidden)]
339 #[deprecated(note = "use `env_clear_cargo` instead")]
340 pub fn env_clear_cargo_vars(&mut self) -> &mut Self {
341 self.env_clear_cargo()
342 }
343
344 /// Removes an explicitly set environment variable and prevents inheriting
345 /// it from a parent process.
346 ///
347 /// This method will ensure that the specified environment variable is not
348 /// present in the spawned process's environment, even if it was present
349 /// in the parent process. This serves to "unset" environment variables.
350 ///
351 /// Note that environment variable names are case-insensitive (but
352 /// case-preserving) on Windows and case-sensitive on all other platforms.
353 ///
354 /// # Examples
355 ///
356 /// Basic usage:
357 ///
358 /// ```no_run
359 /// use cargo_hyperlight::cargo;
360 ///
361 /// cargo()
362 /// .unwrap()
363 /// .env_remove("CARGO_TARGET_DIR")
364 /// .arg("build")
365 /// .exec();
366 /// ```
367 pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
368 self.envs.insert(key.as_ref().to_owned(), None);
369 self
370 }
371
372 /// Inserts or updates multiple explicit environment variable mappings.
373 ///
374 /// This method allows you to add multiple environment variable mappings
375 /// to the spawned process or overwrite variables if they already exist.
376 /// Environment variables can be passed as a `HashMap` or any other type
377 /// implementing `IntoIterator` with the appropriate item type.
378 ///
379 /// Child processes will inherit environment variables from their parent process by
380 /// default. Environment variables explicitly set using [`env`] take precedence
381 /// over inherited variables. You can disable environment variable inheritance entirely
382 /// using [`env_clear`] or for a single key using [`env_remove`].
383 ///
384 /// Note that environment variable names are case-insensitive (but
385 /// case-preserving) on Windows and case-sensitive on all other platforms.
386 ///
387 /// # Examples
388 ///
389 /// Basic usage:
390 ///
391 /// ```no_run
392 /// use std::collections::HashMap;
393 /// use cargo_hyperlight::cargo;
394 ///
395 /// let mut envs = HashMap::new();
396 /// envs.insert("CARGO_TARGET_DIR", "/path/to/target");
397 /// envs.insert("CARGO_HOME", "/path/to/.cargo");
398 ///
399 /// cargo()
400 /// .unwrap()
401 /// .envs(&envs)
402 /// .arg("build")
403 /// .exec();
404 /// ```
405 ///
406 /// ```no_run
407 /// use cargo_hyperlight::cargo;
408 ///
409 /// cargo()
410 /// .unwrap()
411 /// .envs([
412 /// ("CARGO_TARGET_DIR", "/path/to/target"),
413 /// ("CARGO_HOME", "/path/to/.cargo"),
414 /// ])
415 /// .arg("build")
416 /// .exec();
417 /// ```
418 ///
419 /// [`env`]: Command::env
420 /// [`env_clear`]: Command::env_clear
421 /// [`env_remove`]: Command::env_remove
422 pub fn envs(
423 &mut self,
424 envs: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
425 ) -> &mut Self {
426 for (k, v) in envs {
427 self.env(k, v);
428 }
429 self
430 }
431
432 /// Returns an iterator over the arguments that will be passed to the cargo program.
433 ///
434 /// This does not include the program name itself (which can be retrieved with
435 /// [`get_program`]).
436 ///
437 /// # Examples
438 ///
439 /// ```no_run
440 /// use cargo_hyperlight::cargo;
441 ///
442 /// let mut command = cargo().unwrap();
443 /// command.arg("build").arg("--release");
444 ///
445 /// let args: Vec<&std::ffi::OsStr> = command.get_args().collect();
446 /// assert_eq!(args, &["build", "--release"]);
447 /// ```
448 ///
449 /// [`get_program`]: Command::get_program
450 pub fn get_args(&'_ self) -> impl Iterator<Item = &OsStr> {
451 self.args.iter().map(|s| s.as_os_str())
452 }
453
454 /// Returns the working directory for the child process.
455 ///
456 /// This returns `None` if the working directory will not be changed from
457 /// the current directory of the parent process.
458 ///
459 /// # Examples
460 ///
461 /// ```no_run
462 /// use std::path::Path;
463 /// use cargo_hyperlight::cargo;
464 ///
465 /// let mut command = cargo().unwrap();
466 /// assert_eq!(command.get_current_dir(), None);
467 ///
468 /// command.current_dir("/tmp");
469 /// assert_eq!(command.get_current_dir(), Some(Path::new("/tmp")));
470 /// ```
471 pub fn get_current_dir(&self) -> Option<&Path> {
472 self.current_dir.as_deref()
473 }
474
475 /// Returns an iterator over the environment mappings that will be set for the child process.
476 ///
477 /// Environment variables explicitly set or unset via [`env`], [`envs`], and
478 /// [`env_remove`] can be retrieved with this method.
479 ///
480 /// Note that this output does not include environment variables inherited from the
481 /// parent process.
482 ///
483 /// Each element is a tuple key/value where `None` means the variable is explicitly
484 /// unset in the child process environment.
485 ///
486 /// # Examples
487 ///
488 /// ```no_run
489 /// use std::ffi::OsStr;
490 /// use cargo_hyperlight::cargo;
491 ///
492 /// let mut command = cargo().unwrap();
493 /// command.env("CARGO_HOME", "/path/to/.cargo");
494 /// command.env_remove("CARGO_TARGET_DIR");
495 ///
496 /// for (key, value) in command.get_envs() {
497 /// println!("{key:?} => {value:?}");
498 /// }
499 /// ```
500 ///
501 /// [`env`]: Command::env
502 /// [`envs`]: Command::envs
503 /// [`env_remove`]: Command::env_remove
504 pub fn get_envs(&'_ self) -> impl Iterator<Item = (&OsStr, Option<&OsStr>)> {
505 self.envs.iter().map(|(k, v)| (k.as_os_str(), v.as_deref()))
506 }
507
508 /// Returns the base environment variables for the command.
509 ///
510 /// This method returns the environment variables that will be inherited
511 /// from the current process, taking into account whether [`env_clear`] has been called.
512 ///
513 /// [`env_clear`]: Command::env_clear
514 fn base_env(&self) -> impl Iterator<Item = (OsString, OsString)> {
515 env::vars_os().filter(|(k, _)| {
516 if !self.inherit_envs {
517 false
518 } else if !self.inherit_cargo_envs {
519 should_preserve_cargo_env(k)
520 } else {
521 true
522 }
523 })
524 }
525
526 fn resolve_env(&self) -> HashMap<OsString, OsString> {
527 merge_env(self.base_env(), self.get_envs())
528 }
529
530 fn command(&self) -> StdCommand {
531 let mut command = self.cargo.command();
532 command.args(self.get_args());
533 if let Some(cwd) = &self.current_dir {
534 command.current_dir(cwd);
535 }
536 if !self.inherit_envs {
537 command.env_clear();
538 }
539 if !self.inherit_cargo_envs {
540 for (k, _) in env::vars_os() {
541 if !should_preserve_cargo_env(&k) {
542 command.env_remove(k);
543 }
544 }
545 }
546 if let Some(rustup_toolchain) = &self.cargo.rustup_toolchain {
547 command.env("RUSTUP_TOOLCHAIN", rustup_toolchain);
548 }
549 for (k, v) in self.get_envs() {
550 match v {
551 Some(v) => command.env(k, v),
552 None => command.env_remove(k),
553 };
554 }
555 command
556 }
557
558 /// Returns the path to the cargo program that will be executed.
559 ///
560 /// # Examples
561 ///
562 /// ```no_run
563 /// use cargo_hyperlight::cargo;
564 ///
565 /// let command = cargo().unwrap();
566 /// println!("Program: {:?}", command.get_program());
567 /// ```
568 pub fn get_program(&self) -> &OsStr {
569 self.cargo.path.as_os_str()
570 }
571
572 fn build_args(&self) -> Args {
573 // parse the arguments and environment variables
574 match Args::parse(
575 self.get_args(),
576 self.resolve_env(),
577 self.get_current_dir(),
578 Warning::WARN,
579 ) {
580 Ok(args) => args,
581 }
582 }
583
584 fn build_args_infallible(&self) -> Args {
585 match Args::parse(
586 self.get_args(),
587 self.resolve_env(),
588 self.get_current_dir(),
589 Warning::IGNORE,
590 ) {
591 Ok(args) => args,
592 Err(err) => {
593 eprintln!("Failed to parse arguments: {err}");
594 std::process::exit(1);
595 }
596 }
597 }
598
599 /// Executes a cargo command as a child process, waiting for it to finish and
600 /// collecting its exit status.
601 ///
602 /// The process stdin, stdout and stderr are inherited from the parent.
603 ///
604 /// # Examples
605 ///
606 /// Basic usage:
607 ///
608 /// ```no_run
609 /// use cargo_hyperlight::cargo;
610 ///
611 /// let result = cargo()
612 /// .unwrap()
613 /// .arg("build")
614 /// .status();
615 ///
616 /// match result {
617 /// Ok(()) => println!("Cargo command succeeded"),
618 /// Err(e) => println!("Cargo command failed: {}", e),
619 /// }
620 /// ```
621 ///
622 /// # Errors
623 ///
624 /// This method will return an error if:
625 /// - The sysroot preparation fails
626 /// - The cargo process could not be spawned
627 /// - The cargo process returned a non-zero exit status
628 pub fn status(&self) -> anyhow::Result<()> {
629 let args = self.build_args();
630
631 args.prepare_sysroot()
632 .context("Failed to prepare sysroot")?;
633
634 self.command()
635 .populate_from_args(&args)
636 .checked_status()
637 .context("Failed to execute cargo")?;
638 Ok(())
639 }
640
641 /// Executes the cargo command, replacing the current process.
642 ///
643 /// This function will never return on success, as it replaces the current process
644 /// with the cargo process. On error, it will print the error and exit with code 101.
645 ///
646 /// # Examples
647 ///
648 /// Basic usage:
649 ///
650 /// ```no_run
651 /// use cargo_hyperlight::cargo;
652 ///
653 /// cargo()
654 /// .unwrap()
655 /// .arg("build")
656 /// .exec(); // This will never return
657 /// ```
658 ///
659 /// # Errors
660 ///
661 /// This function will exit the process with code 101 if:
662 /// - The sysroot preparation fails
663 /// - The process replacement fails
664 pub fn exec(&self) -> ! {
665 match self.exec_impl() {
666 Err(e) => {
667 eprintln!("{e:?}");
668 std::process::exit(101);
669 }
670 }
671 }
672
673 /// Internal implementation of process replacement.
674 ///
675 /// This method prepares the sysroot and then calls the low-level `exec` function
676 /// to replace the current process.
677 fn exec_impl(&self) -> anyhow::Result<Infallible> {
678 let args = self.build_args();
679
680 args.prepare_sysroot()
681 .context("Failed to prepare sysroot")?;
682
683 let mut command = self.command();
684 command.populate_from_args(&args);
685
686 if let Some(cwd) = self.get_current_dir() {
687 env::set_current_dir(cwd).context("Failed to change current directory")?;
688 }
689
690 Ok(exec(
691 command.get_program(),
692 command.get_args(),
693 command.resolve_env(self.base_env()),
694 )?)
695 }
696}
697
698/// Replaces the current process with the specified program using `execvpe`.
699///
700/// This function converts the provided arguments and environment variables into
701/// the format expected by the `execvpe` system call and then replaces the current
702/// process with the new program.
703///
704/// # Arguments
705///
706/// * `program` - The path to the program to execute
707/// * `args` - The command-line arguments to pass to the program
708/// * `envs` - The environment variables to set for the new process
709///
710/// # Returns
711///
712/// This function should never return on success. On failure, it returns an
713/// `std::io::Error` describing what went wrong.
714///
715/// # Safety
716///
717/// This function uses unsafe code to call `libc::execvpe`. The implementation
718/// carefully manages memory to ensure null-terminated strings are properly
719/// constructed for the system call.
720fn exec(
721 program: impl AsRef<OsStr>,
722 args: impl IntoIterator<Item = impl AsRef<OsStr>>,
723 envs: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
724) -> std::io::Result<Infallible> {
725 let mut env_bytes = vec![];
726 let mut env_offsets = vec![];
727 for (k, v) in envs.into_iter() {
728 env_offsets.push(env_bytes.len());
729 env_bytes.extend_from_slice(k.as_ref().as_encoded_bytes());
730 env_bytes.push(b'=');
731 env_bytes.extend_from_slice(v.as_ref().as_encoded_bytes());
732 env_bytes.push(0);
733 }
734 let env_ptrs = env_offsets
735 .into_iter()
736 .map(|offset| env_bytes[offset..].as_ptr() as *const c_char)
737 .chain(iter::once(std::ptr::null()))
738 .collect::<Vec<_>>();
739
740 let mut arg_bytes = vec![];
741 let mut arg_offsets = vec![];
742
743 arg_offsets.push(arg_bytes.len());
744 arg_bytes.extend_from_slice(program.as_ref().as_encoded_bytes());
745 arg_bytes.push(0);
746
747 for arg in args {
748 arg_offsets.push(arg_bytes.len());
749 arg_bytes.extend_from_slice(arg.as_ref().as_encoded_bytes());
750 arg_bytes.push(0);
751 }
752 let arg_ptrs = arg_offsets
753 .into_iter()
754 .map(|offset| arg_bytes[offset..].as_ptr() as *const c_char)
755 .chain(iter::once(std::ptr::null()))
756 .collect::<Vec<_>>();
757
758 unsafe { libc::execvpe(arg_ptrs[0], arg_ptrs.as_ptr(), env_ptrs.as_ptr()) };
759
760 Err(std::io::Error::last_os_error())
761}
762
763/// Returns `true` if the given environment variable should be preserved
764/// when clearing Cargo build-context variables.
765///
766/// Non-`CARGO_` variables are always preserved. The following `CARGO_`
767/// user configuration variables are also preserved:
768/// - `CARGO_HOME` — Cargo home directory
769/// - `CARGO_REGISTRIES_*` — Private registry index URLs, tokens, and credential providers
770/// - `CARGO_REGISTRY_*` — Default registry and crates.io credentials
771/// - `CARGO_HTTP_*` — HTTP/TLS proxy and timeout settings
772/// - `CARGO_NET_*` — Network configuration (retry, offline, git-fetch-with-cli)
773/// - `CARGO_ALIAS_*` — Command aliases
774/// - `CARGO_TERM_*` — Terminal output settings
775///
776/// All other `CARGO_*` variables (e.g. `CARGO_PKG_*`, `CARGO_MANIFEST_*`,
777/// `CARGO_CFG_*`, `CARGO_FEATURE_*`, `CARGO_MAKEFLAGS`, etc.) are considered
778/// build-context variables set by Cargo during compilation and will be cleared.
779///
780/// See: <https://doc.rust-lang.org/cargo/reference/environment-variables.html>
781fn should_preserve_cargo_env(key: &OsStr) -> bool {
782 !key.starts_with("CARGO_")
783 || key == "CARGO_HOME"
784 || key.starts_with("CARGO_REGISTRIES_")
785 || key.starts_with("CARGO_REGISTRY_")
786 || key.starts_with("CARGO_HTTP_")
787 || key.starts_with("CARGO_NET_")
788 || key.starts_with("CARGO_ALIAS_")
789 || key.starts_with("CARGO_TERM_")
790}