1use crate::config::{
4 self, supported_freebsd_versions_str, supported_glibc_versions_str,
5 supported_iphone_sdk_versions_str, supported_macos_sdk_versions_str,
6 DEFAULT_CROSS_MAKE_VERSION, DEFAULT_FREEBSD_VERSION, DEFAULT_GLIBC_VERSION,
7 DEFAULT_IPHONE_SDK_VERSION, DEFAULT_MACOS_SDK_VERSION, DEFAULT_NDK_VERSION,
8 DEFAULT_QEMU_VERSION, SUPPORTED_FREEBSD_VERSIONS, SUPPORTED_GLIBC_VERSIONS,
9 SUPPORTED_IPHONE_SDK_VERSIONS, SUPPORTED_MACOS_SDK_VERSIONS,
10};
11use crate::error::{CrossError, Result};
12use clap::builder::styling::{AnsiColor, Effects, Styles};
13use clap::ArgAction;
14use clap::{Args as ClapArgs, CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint};
15use std::collections::HashMap;
16use std::path::PathBuf;
17use std::sync::LazyLock;
18
19const BIN_NAME: &str = env!("CARGO_PKG_NAME");
21
22macro_rules! define_subcommand {
24 ($name:literal) => {
25 const SUBCOMMAND: &str = $name;
26 const CARGO_DISPLAY_NAME: &str = concat!("cargo ", $name);
27 };
28}
29define_subcommand!("cross");
30
31static PROGRAM_NAME: LazyLock<&'static str> = LazyLock::new(|| {
33 let args: Vec<String> = std::env::args().collect();
34 let is_cargo_subcommand = std::env::var("CARGO").is_ok()
35 && std::env::var("CARGO_HOME").is_ok()
36 && args.get(1).map(String::as_str) == Some(SUBCOMMAND);
37
38 if is_cargo_subcommand {
39 CARGO_DISPLAY_NAME
40 } else {
41 BIN_NAME
42 }
43});
44
45#[must_use]
47pub fn program_name() -> &'static str {
48 *PROGRAM_NAME
49}
50
51fn cli_styles() -> Styles {
53 Styles::styled()
54 .header(AnsiColor::BrightCyan.on_default() | Effects::BOLD)
55 .usage(AnsiColor::BrightCyan.on_default() | Effects::BOLD)
56 .literal(AnsiColor::BrightGreen.on_default())
57 .placeholder(AnsiColor::BrightMagenta.on_default())
58 .valid(AnsiColor::BrightGreen.on_default())
59 .invalid(AnsiColor::BrightRed.on_default())
60 .error(AnsiColor::BrightRed.on_default() | Effects::BOLD)
61}
62
63#[derive(Parser, Debug)]
65#[command(name = "cargo-cross", version)]
66#[command(about = "Cross-compilation tool for Rust projects, no Docker required")]
67#[command(long_about = "\
68Cross-compilation tool for Rust projects.
69
70This tool provides cross-compilation support for Rust projects across multiple
71platforms including Linux (musl/gnu), Windows, macOS, FreeBSD, iOS, and Android.
72It automatically downloads and configures the appropriate cross-compiler toolchains.")]
73#[command(propagate_version = true)]
74#[command(arg_required_else_help = true)]
75#[command(styles = cli_styles())]
76#[command(override_usage = "cargo-cross [+toolchain] <COMMAND> [OPTIONS]")]
77#[command(after_help = "\
78Use 'cargo-cross <COMMAND> --help' for more information about a command.
79
80TOOLCHAIN:
81 If the first argument begins with +, it will be interpreted as a Rust toolchain
82 name (such as +nightly, +stable, or +1.75.0). This follows the same convention
83 as rustup and cargo.
84
85EXAMPLES:
86 cargo-cross build -t x86_64-unknown-linux-musl
87 cargo-cross +nightly build -t aarch64-unknown-linux-gnu --profile release
88 cargo-cross build -t '*-linux-musl' --crt-static true
89 cargo-cross test -t x86_64-unknown-linux-musl -- --nocapture")]
90pub struct Cli {
91 #[command(subcommand)]
92 pub command: CliCommand,
93}
94
95#[derive(Subcommand, Debug)]
96pub enum CliCommand {
97 #[command(visible_alias = "b")]
99 #[command(long_about = "\
100Compile the current package and all of its dependencies.
101
102When no target selection options are given, this tool will build all binary
103and library targets of the selected packages.")]
104 Build(BuildArgs),
105
106 #[command(visible_alias = "c")]
108 #[command(long_about = "\
109Check the current package and all of its dependencies for errors.
110
111This will essentially compile packages without performing the final step of
112code generation, which is faster than running build.")]
113 Check(BuildArgs),
114
115 #[command(visible_alias = "r")]
117 #[command(long_about = "\
118Run a binary or example of the local package.
119
120For cross-compilation targets, QEMU user-mode emulation is used to run the binary.")]
121 Run(BuildArgs),
122
123 #[command(visible_alias = "t")]
125 #[command(long_about = "\
126Execute all unit and integration tests and build examples of a local package.
127
128For cross-compilation targets, QEMU user-mode emulation is used to run tests.")]
129 Test(BuildArgs),
130
131 #[command(long_about = "\
133Execute all benchmarks of a local package.
134
135For cross-compilation targets, QEMU user-mode emulation is used to run benchmarks.")]
136 Bench(BuildArgs),
137
138 #[command(long_about = "\
140Run Clippy on the current package and all of its dependencies.
141
142This forwards to `cargo clippy` with the configured cross-compilation environment.")]
143 Clippy(BuildArgs),
144
145 #[command(long_about = "\
147Prepare the cross-compilation environment and print environment variables.
148
149This is intended for use with shell evaluation, for example:
150 eval \"$(cargo cross setup -t aarch64-unknown-linux-musl)\"")]
151 Setup(SetupCliArgs),
152
153 #[command(long_about = "\
155Prepare the cross-compilation environment and execute an arbitrary command.
156
157This is useful for running custom tooling that should inherit the configured
158compiler, linker, PATH, and cargo target environment variables.")]
159 Exec(ExecCliArgs),
160
161 #[command(long_about = "\
163Display all supported cross-compilation targets.
164
165You can also use glob patterns with --target to match multiple targets,
166for example: --target '*-linux-musl' or --target 'aarch64-*'")]
167 Targets(TargetsArgs),
168
169 Version,
171}
172
173#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)]
175pub enum OutputFormat {
176 #[default]
178 Text,
179 Json,
181 Plain,
183}
184
185#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)]
187pub enum SetupOutputFormat {
188 #[default]
190 Auto,
191 Bash,
193 Zsh,
195 Fish,
197 Powershell,
199 Cmd,
201 Json,
203}
204
205#[derive(ClapArgs, Debug, Clone)]
206pub struct SetupCliArgs {
207 #[command(flatten)]
208 pub build: BuildArgs,
209
210 #[arg(
212 short = 'f',
213 long = "format",
214 value_enum,
215 default_value = "auto",
216 help = "Output format (auto, bash, zsh, fish, powershell, cmd, json)"
217 )]
218 pub format: SetupOutputFormat,
219}
220
221#[derive(Debug, Clone)]
222pub struct SetupArgs {
223 pub args: Args,
224 pub format: SetupOutputFormat,
225}
226
227#[derive(ClapArgs, Debug, Clone)]
228pub struct ExecCliArgs {
229 #[command(flatten)]
230 pub build: BuildArgs,
231}
232
233#[derive(Debug, Clone)]
234pub struct ExecArgs {
235 pub args: Args,
236 pub command: Vec<String>,
237}
238
239#[derive(ClapArgs, Debug, Clone, Default)]
240pub struct TargetsArgs {
241 #[arg(
243 short = 'f',
244 long = "format",
245 value_enum,
246 default_value = "text",
247 help = "Output format (text, json, plain)"
248 )]
249 pub format: OutputFormat,
250}
251
252#[derive(ClapArgs, Debug, Clone, Default)]
253#[command(next_help_heading = "Target Selection")]
254pub struct BuildArgs {
255 #[arg(
258 short = 't',
259 long = "target",
260 visible_alias = "targets",
261 value_delimiter = ',',
262 env = "TARGETS",
263 value_name = "TRIPLE",
264 help = "Build for the target triple(s), comma-separated",
265 long_help = "\
266Build for the specified target architecture. This flag may be specified multiple
267times or with comma-separated values. Supports glob patterns like '*-linux-musl'.
268The general format of the triple is <arch><sub>-<vendor>-<sys>-<abi>.
269Run the 'targets' subcommand to see all supported targets.
270
271Examples: -t x86_64-unknown-linux-musl, -t '*-linux-musl'"
272 )]
273 pub targets: Vec<String>,
274
275 #[arg(
278 short = 'F',
279 long,
280 env = "FEATURES",
281 value_name = "FEATURES",
282 conflicts_with = "all_features",
283 help_heading = "Feature Selection",
284 long_help = "\
285Space or comma separated list of features to activate. Features of workspace members
286may be enabled with package-name/feature-name syntax. May be specified multiple times."
287 )]
288 pub features: Option<String>,
289
290 #[arg(long, env = "NO_DEFAULT_FEATURES", help_heading = "Feature Selection")]
292 pub no_default_features: bool,
293
294 #[arg(
296 long,
297 env = "ALL_FEATURES",
298 conflicts_with = "features",
299 help_heading = "Feature Selection"
300 )]
301 pub all_features: bool,
302
303 #[arg(
306 short = 'r',
307 long = "release",
308 conflicts_with = "profile",
309 help_heading = "Profile",
310 long_help = "\
311Build artifacts in release mode, with optimizations. Equivalent to --profile=release."
312 )]
313 pub release: bool,
314
315 #[arg(
317 long,
318 default_value = "dev",
319 env = "PROFILE",
320 value_name = "PROFILE-NAME",
321 conflicts_with = "release",
322 help_heading = "Profile",
323 long_help = "\
324Build artifacts with the specified profile. Built-in: dev, release, test, bench.
325Custom profiles can be defined in Cargo.toml. Default is 'dev'."
326 )]
327 pub profile: String,
328
329 #[arg(
332 short = 'p',
333 long,
334 env = "PACKAGE",
335 value_name = "SPEC",
336 help_heading = "Package Selection",
337 long_help = "\
338Build only the specified packages. This flag may be specified multiple times
339and supports common Unix glob patterns like *, ?, and []."
340 )]
341 pub package: Option<String>,
342
343 #[arg(
345 long,
346 visible_alias = "all",
347 env = "BUILD_WORKSPACE",
348 help_heading = "Package Selection"
349 )]
350 pub workspace: bool,
351
352 #[arg(
354 long,
355 env = "EXCLUDE",
356 value_name = "SPEC",
357 requires = "workspace",
358 help_heading = "Package Selection",
359 long_help = "\
360Exclude the specified packages. Must be used in conjunction with the --workspace flag.
361This flag may be specified multiple times and supports common Unix glob patterns."
362 )]
363 pub exclude: Option<String>,
364
365 #[arg(
367 long = "bin",
368 env = "BIN_TARGET",
369 value_name = "NAME",
370 help_heading = "Package Selection",
371 long_help = "\
372Build the specified binary. This flag may be specified multiple times
373and supports common Unix glob patterns."
374 )]
375 pub bin_target: Option<String>,
376
377 #[arg(long = "bins", env = "BUILD_BINS", help_heading = "Package Selection")]
379 pub build_bins: bool,
380
381 #[arg(long = "lib", env = "BUILD_LIB", help_heading = "Package Selection")]
383 pub build_lib: bool,
384
385 #[arg(
387 long = "example",
388 env = "EXAMPLE_TARGET",
389 value_name = "NAME",
390 help_heading = "Package Selection",
391 long_help = "\
392Build the specified example. This flag may be specified multiple times
393and supports common Unix glob patterns."
394 )]
395 pub example_target: Option<String>,
396
397 #[arg(
399 long = "examples",
400 env = "BUILD_EXAMPLES",
401 help_heading = "Package Selection"
402 )]
403 pub build_examples: bool,
404
405 #[arg(
407 long = "test",
408 env = "TEST_TARGET",
409 value_name = "NAME",
410 help_heading = "Package Selection",
411 long_help = "\
412Build the specified integration test. This flag may be specified multiple times
413and supports common Unix glob patterns."
414 )]
415 pub test_target: Option<String>,
416
417 #[arg(
419 long = "tests",
420 env = "BUILD_TESTS",
421 help_heading = "Package Selection",
422 long_help = "\
423Build all targets that have the test = true manifest flag set. By default this
424includes the library and binaries built as unittests, and integration tests."
425 )]
426 pub build_tests: bool,
427
428 #[arg(
430 long = "bench",
431 env = "BENCH_TARGET",
432 value_name = "NAME",
433 help_heading = "Package Selection",
434 long_help = "\
435Build the specified benchmark. This flag may be specified multiple times
436and supports common Unix glob patterns."
437 )]
438 pub bench_target: Option<String>,
439
440 #[arg(
442 long = "benches",
443 env = "BUILD_BENCHES",
444 help_heading = "Package Selection",
445 long_help = "\
446Build all targets that have the bench = true manifest flag set. By default this
447includes the library and binaries built as benchmarks, and bench targets."
448 )]
449 pub build_benches: bool,
450
451 #[arg(
453 long = "all-targets",
454 env = "BUILD_ALL_TARGETS",
455 help_heading = "Package Selection"
456 )]
457 pub build_all_targets: bool,
458
459 #[arg(long, env = "MANIFEST_PATH", value_name = "PATH",
461 value_hint = ValueHint::FilePath, help_heading = "Package Selection",
462 long_help = "\
463Path to Cargo.toml. By default, Cargo searches for the Cargo.toml file
464in the current directory or any parent directory.")]
465 pub manifest_path: Option<PathBuf>,
466
467 #[arg(long, default_value = DEFAULT_GLIBC_VERSION, env = "GLIBC_VERSION",
470 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
471 pub glibc_version: String,
472
473 #[arg(long, default_value = DEFAULT_IPHONE_SDK_VERSION, env = "IPHONE_SDK_VERSION",
475 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
476 pub iphone_sdk_version: String,
477
478 #[arg(long, env = "IPHONE_SDK_PATH", value_name = "PATH",
480 value_hint = ValueHint::DirPath, help_heading = "Toolchain Versions",
481 long_help = "\
482Override iPhoneOS SDK path for device targets. Skips version lookup.")]
483 pub iphone_sdk_path: Option<PathBuf>,
484
485 #[arg(long, env = "IPHONE_SIMULATOR_SDK_PATH", value_name = "PATH",
487 value_hint = ValueHint::DirPath, help_heading = "Toolchain Versions",
488 long_help = "\
489Override iPhoneSimulator SDK path for simulator targets. Skips version lookup.")]
490 pub iphone_simulator_sdk_path: Option<PathBuf>,
491
492 #[arg(long, default_value = DEFAULT_MACOS_SDK_VERSION, env = "MACOS_SDK_VERSION",
494 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
495 pub macos_sdk_version: String,
496
497 #[arg(long, env = "MACOS_SDK_PATH", value_name = "PATH",
499 value_hint = ValueHint::DirPath, help_heading = "Toolchain Versions",
500 long_help = "\
501Override macOS SDK path directly. Skips version lookup.")]
502 pub macos_sdk_path: Option<PathBuf>,
503
504 #[arg(long, default_value = DEFAULT_FREEBSD_VERSION, env = "FREEBSD_VERSION",
506 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
507 pub freebsd_version: String,
508
509 #[arg(long, default_value = DEFAULT_NDK_VERSION, env = "NDK_VERSION",
511 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions",
512 long_help = "\
513Specify Android NDK version for Android targets. Auto-downloaded from Google's official repository.")]
514 pub ndk_version: String,
515
516 #[arg(long, default_value = DEFAULT_QEMU_VERSION, env = "QEMU_VERSION",
518 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions",
519 long_help = "\
520Specify QEMU version for user-mode emulation. Used to run cross-compiled binaries during test/run/bench.")]
521 pub qemu_version: String,
522
523 #[arg(long, default_value = DEFAULT_CROSS_MAKE_VERSION, env = "CROSS_MAKE_VERSION",
525 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions",
526 long_help = "\
527Specify cross-compiler make version. This determines which version of cross-compilation \
528toolchains will be downloaded from the upstream repository.")]
529 pub cross_make_version: String,
530
531 #[arg(long, env = "CROSS_COMPILER_DIR", value_name = "DIR",
534 value_hint = ValueHint::DirPath, help_heading = "Directories",
535 long_help = "\
536Directory where cross-compiler toolchains will be downloaded and stored. Defaults to temp dir.
537Set this to reuse downloaded toolchains across builds.")]
538 pub cross_compiler_dir: Option<PathBuf>,
539
540 #[arg(long, visible_alias = "target-dir", env = "CARGO_TARGET_DIR", value_name = "DIR",
542 value_hint = ValueHint::DirPath, help_heading = "Directories",
543 long_help = "\
544Directory for all generated artifacts and intermediate files. Defaults to 'target'.")]
545 pub cargo_target_dir: Option<PathBuf>,
546
547 #[arg(long, env = "ARTIFACT_DIR", value_name = "DIR",
549 value_hint = ValueHint::DirPath, help_heading = "Directories",
550 long_help = "\
551Copy final artifacts to this directory. Unstable, requires nightly toolchain.")]
552 pub artifact_dir: Option<PathBuf>,
553
554 #[arg(long, env = "CC", value_name = "PATH",
557 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
558 long_help = "\
559Override the C compiler path. By default, the appropriate cross-compiler is auto-configured.")]
560 pub cc: Option<PathBuf>,
561
562 #[arg(long, env = "CXX", value_name = "PATH",
564 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
565 long_help = "\
566Override the C++ compiler path. By default, the appropriate cross-compiler is auto-configured.")]
567 pub cxx: Option<PathBuf>,
568
569 #[arg(long, env = "AR", value_name = "PATH",
571 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
572 long_help = "\
573Override the archiver (ar) path. By default, the appropriate archiver is auto-configured.")]
574 pub ar: Option<PathBuf>,
575
576 #[arg(long, env = "LINKER", value_name = "PATH",
578 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
579 long_help = "\
580Override the linker path. By default, the cross-compiler is used as linker.
581This option takes precedence over auto-configured linker.")]
582 pub linker: Option<PathBuf>,
583
584 #[arg(
586 long,
587 env = "CFLAGS",
588 value_name = "FLAGS",
589 allow_hyphen_values = true,
590 help_heading = "Compiler Options",
591 long_help = "\
592Additional flags to pass to the C compiler. Appended to default CFLAGS.
593Example: --cflags '-O2 -Wall -march=native'"
594 )]
595 pub cflags: Option<String>,
596
597 #[arg(
599 long,
600 env = "CXXFLAGS",
601 value_name = "FLAGS",
602 allow_hyphen_values = true,
603 help_heading = "Compiler Options",
604 long_help = "\
605Additional flags to pass to the C++ compiler. Appended to default CXXFLAGS.
606Example: --cxxflags '-O2 -Wall -std=c++17'"
607 )]
608 pub cxxflags: Option<String>,
609
610 #[arg(
612 long,
613 env = "LDFLAGS",
614 value_name = "FLAGS",
615 allow_hyphen_values = true,
616 help_heading = "Compiler Options",
617 long_help = "\
618Additional flags to pass to the linker. Appended to default LDFLAGS.
619Example: --ldflags '-L/usr/local/lib -static'"
620 )]
621 pub ldflags: Option<String>,
622
623 #[arg(
625 long,
626 env = "CXXSTDLIB",
627 value_name = "LIB",
628 help_heading = "Compiler Options",
629 long_help = "\
630Specify the C++ standard library to use (libc++, libstdc++, etc)."
631 )]
632 pub cxxstdlib: Option<String>,
633
634 #[arg(
636 long,
637 short = 'G',
638 env = "CMAKE_GENERATOR",
639 value_name = "GENERATOR",
640 help_heading = "Compiler Options",
641 long_help = "\
642Specify the CMake generator to use. On Windows, this overrides the auto-detection.
643Common generators: Ninja, 'MinGW Makefiles', 'Unix Makefiles', 'NMake Makefiles'.
644If not specified, auto-detects: Ninja > MinGW Makefiles > Unix Makefiles."
645 )]
646 pub cmake_generator: Option<String>,
647
648 #[arg(long = "rustflag", visible_alias = "rustflags", value_name = "FLAG",
650 env = "ADDITIONAL_RUSTFLAGS", allow_hyphen_values = true,
651 action = clap::ArgAction::Append, help_heading = "Compiler Options",
652 long_help = "\
653Additional flags to pass to rustc via RUSTFLAGS. Can be specified multiple times.
654Example: --rustflag '-C target-cpu=native' --rustflag '-C lto=thin'")]
655 pub rustflags: Vec<String>,
656
657 #[arg(long, env = "RUSTC_WRAPPER", value_name = "PATH",
659 value_hint = ValueHint::ExecutablePath,
660 conflicts_with = "enable_sccache", help_heading = "Compiler Options",
661 long_help = "\
662Specify a rustc wrapper program (sccache, cachepot, etc) for compilation caching.")]
663 pub rustc_wrapper: Option<PathBuf>,
664
665 #[arg(
667 long,
668 env = "NO_TOOLCHAIN_SETUP",
669 help_heading = "Compiler Options",
670 long_help = "\
671Skip downloading and configuring cross-compilation toolchain.
672Use this when you have pre-configured system compilers or want to use
673only CLI-provided compiler options (--cc, --cxx, --ar, --linker)."
674 )]
675 pub no_toolchain_setup: bool,
676
677 #[arg(
680 long,
681 env = "ENABLE_SCCACHE",
682 conflicts_with = "rustc_wrapper",
683 help_heading = "Sccache Options",
684 long_help = "\
685Enable sccache as the rustc wrapper for compilation caching.
686Speeds up compilation by caching previous compilations."
687 )]
688 pub enable_sccache: bool,
689
690 #[arg(long, env = "SCCACHE_DIR", value_name = "DIR",
692 value_hint = ValueHint::DirPath, help_heading = "Sccache Options",
693 long_help = "\
694Directory for sccache's local disk cache. Defaults to $HOME/.cache/sccache.")]
695 pub sccache_dir: Option<PathBuf>,
696
697 #[arg(
699 long,
700 env = "SCCACHE_CACHE_SIZE",
701 value_name = "SIZE",
702 help_heading = "Sccache Options",
703 long_help = "\
704Maximum size of the local disk cache (e.g., '10G', '500M'). Default is 10GB."
705 )]
706 pub sccache_cache_size: Option<String>,
707
708 #[arg(
710 long,
711 env = "SCCACHE_IDLE_TIMEOUT",
712 value_name = "SECONDS",
713 help_heading = "Sccache Options",
714 long_help = "\
715Idle timeout in seconds for the sccache server. Set to 0 to run indefinitely."
716 )]
717 pub sccache_idle_timeout: Option<String>,
718
719 #[arg(
721 long,
722 env = "SCCACHE_LOG",
723 value_name = "LEVEL",
724 help_heading = "Sccache Options",
725 long_help = "\
726Log level for sccache. Valid: error, warn, info, debug, trace"
727 )]
728 pub sccache_log: Option<String>,
729
730 #[arg(
732 long,
733 env = "SCCACHE_NO_DAEMON",
734 help_heading = "Sccache Options",
735 long_help = "\
736Run sccache without daemon (single-process mode). May be slower but avoids daemon startup issues."
737 )]
738 pub sccache_no_daemon: bool,
739
740 #[arg(
742 long,
743 env = "SCCACHE_DIRECT",
744 help_heading = "Sccache Options",
745 long_help = "\
746Enable sccache direct mode. Caches based on source file content directly, bypassing preprocessor."
747 )]
748 pub sccache_direct: bool,
749
750 #[arg(
753 long,
754 env = "CRATE_CC_NO_DEFAULTS",
755 hide = true,
756 help_heading = "CC Crate Options"
757 )]
758 pub cc_no_defaults: bool,
759
760 #[arg(
762 long,
763 env = "CC_SHELL_ESCAPED_FLAGS",
764 hide = true,
765 help_heading = "CC Crate Options"
766 )]
767 pub cc_shell_escaped_flags: bool,
768
769 #[arg(
771 long,
772 env = "CC_ENABLE_DEBUG_OUTPUT",
773 hide = true,
774 help_heading = "CC Crate Options"
775 )]
776 pub cc_enable_debug: bool,
777
778 #[arg(long, value_parser = parse_optional_bool, env = "CRT_STATIC",
781 value_name = "BOOL", num_args = 0..=1, default_missing_value = "true",
782 help_heading = "Build Options",
783 long_help = "\
784Control whether the C runtime is statically linked. true=static (larger, portable),
785false=dynamic (smaller, requires libc). Musl defaults to static, glibc to dynamic.")]
786 pub crt_static: Option<bool>,
787
788 #[arg(
790 long,
791 env = "PANIC_IMMEDIATE_ABORT",
792 help_heading = "Build Options",
793 long_help = "\
794Use panic=abort and remove panic formatting code for smaller binaries.
795Requires nightly and implies --build-std. Stack traces will not be available."
796 )]
797 pub panic_immediate_abort: bool,
798
799 #[arg(long, value_name = "MODE", hide = true, help_heading = "Build Options")]
801 pub fmt_debug: Option<String>,
802
803 #[arg(long, value_name = "MODE", hide = true, help_heading = "Build Options")]
805 pub location_detail: Option<String>,
806
807 #[arg(long, value_parser = parse_build_std, env = "BUILD_STD",
809 value_name = "CRATES", help_heading = "Build Options",
810 num_args = 0..=1, default_missing_value = "true",
811 long_help = "\
812Build the standard library from source (requires nightly). Without arguments, builds 'std'.
813Use 'true' for full std or specify crates like 'core,alloc'. Required for unsupported targets or panic=abort.")]
814 pub build_std: Option<String>,
815
816 #[arg(
818 long,
819 env = "BUILD_STD_FEATURES",
820 value_name = "FEATURES",
821 requires = "build_std",
822 help_heading = "Build Options",
823 long_help = "\
824Space-separated features for std. Common: panic_immediate_abort, optimize_for_size"
825 )]
826 pub build_std_features: Option<String>,
827
828 #[arg(
830 long,
831 visible_alias = "trim-paths",
832 env = "CARGO_TRIM_PATHS",
833 value_name = "VALUE",
834 num_args = 0..=1,
835 default_missing_value = "true",
836 help_heading = "Build Options",
837 long_help = "\
838Control how paths are trimmed in compiler output for reproducible builds.
839Valid: true, macro, diagnostics, object, all, none (default: false)"
840 )]
841 pub cargo_trim_paths: Option<String>,
842
843 #[arg(
845 long,
846 env = "NO_EMBED_METADATA",
847 hide = true,
848 help_heading = "Build Options"
849 )]
850 pub no_embed_metadata: bool,
851
852 #[arg(
854 long,
855 env = "RUSTC_BOOTSTRAP",
856 value_name = "VALUE",
857 num_args = 0..=1,
858 default_missing_value = "1",
859 hide = true,
860 help_heading = "Build Options"
861 )]
862 pub rustc_bootstrap: Option<String>,
863
864 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count,
867 env = "VERBOSE_LEVEL", conflicts_with = "quiet",
868 help_heading = "Output Options",
869 long_help = "\
870Use verbose output. -v=commands/warnings, -vv=+deps/build scripts, -vvv=max verbosity")]
871 pub verbose_level: u8,
872
873 #[arg(
875 short = 'q',
876 long,
877 env = "QUIET",
878 conflicts_with = "verbose_level",
879 help_heading = "Output Options",
880 long_help = "\
881Do not print cargo log messages. Shows only errors and warnings."
882 )]
883 pub quiet: bool,
884
885 #[arg(
887 long,
888 env = "MESSAGE_FORMAT",
889 value_name = "FMT",
890 help_heading = "Output Options",
891 long_help = "\
892Output format for diagnostics. Valid: human (default), short, json"
893 )]
894 pub message_format: Option<String>,
895
896 #[arg(
898 long,
899 env = "COLOR",
900 value_name = "WHEN",
901 help_heading = "Output Options",
902 long_help = "\
903Control when colored output is used. Valid: auto (default), always, never"
904 )]
905 pub color: Option<String>,
906
907 #[arg(long, env = "BUILD_PLAN", hide = true, help_heading = "Output Options")]
909 pub build_plan: bool,
910
911 #[arg(long, env = "TIMINGS", value_name = "FMTS",
913 num_args = 0..=1, default_missing_value = "true",
914 help_heading = "Output Options",
915 long_help = "\
916Output timing information. --timings=HTML report, --timings=json for JSON. Saved to target/cargo-timings/.")]
917 pub timings: Option<String>,
918
919 #[arg(
922 long,
923 env = "IGNORE_RUST_VERSION",
924 help_heading = "Dependency Options",
925 long_help = "\
926Ignore rust-version specification in packages. Allows building with older Rust versions."
927 )]
928 pub ignore_rust_version: bool,
929
930 #[arg(
932 long,
933 env = "LOCKED",
934 help_heading = "Dependency Options",
935 long_help = "\
936Assert Cargo.lock will remain unchanged. Exits with error if missing or needs updating. Use in CI."
937 )]
938 pub locked: bool,
939
940 #[arg(
942 long,
943 env = "OFFLINE",
944 help_heading = "Dependency Options",
945 long_help = "\
946Prevent network access. Uses locally cached data. Run 'cargo fetch' first if needed."
947 )]
948 pub offline: bool,
949
950 #[arg(
952 long,
953 env = "FROZEN",
954 help_heading = "Dependency Options",
955 long_help = "\
956Equivalent to --locked --offline. Requires Cargo.lock and cache are up to date."
957 )]
958 pub frozen: bool,
959
960 #[arg(long, env = "LOCKFILE_PATH", value_name = "PATH",
962 value_hint = ValueHint::FilePath, help_heading = "Dependency Options",
963 long_help = "\
964Override lockfile path from default (<workspace_root>/Cargo.lock). Requires nightly.")]
965 pub lockfile_path: Option<PathBuf>,
966
967 #[arg(
970 short = 'j',
971 long,
972 env = "JOBS",
973 value_name = "N",
974 help_heading = "Build Configuration",
975 long_help = "\
976Number of parallel jobs. Defaults to logical CPUs. Negative=CPUs+N. 'default' to reset."
977 )]
978 pub jobs: Option<String>,
979
980 #[arg(
982 long,
983 env = "KEEP_GOING",
984 help_heading = "Build Configuration",
985 long_help = "\
986Build as many crates in the dependency graph as possible. Rather than aborting on the first
987crate that fails to build, continue with other crates in the dependency graph."
988 )]
989 pub keep_going: bool,
990
991 #[arg(
993 long,
994 env = "FUTURE_INCOMPAT_REPORT",
995 help_heading = "Build Configuration",
996 long_help = "\
997Displays a future-incompat report for any future-incompatible warnings produced during
998execution of this command. See 'cargo report' for more information."
999 )]
1000 pub future_incompat_report: bool,
1001
1002 #[arg(
1006 long,
1007 visible_alias = "args",
1008 value_name = "ARGS",
1009 hide = true,
1010 allow_hyphen_values = true,
1011 action = clap::ArgAction::Append,
1012 help_heading = "Additional Options"
1013 )]
1014 pub cargo_args: Vec<String>,
1015
1016 #[arg(short = 'Z', value_name = "FLAG",
1018 action = clap::ArgAction::Append, help_heading = "Additional Options",
1019 long_help = "\
1020Unstable (nightly-only) flags to Cargo. Run 'cargo -Z help' for details on available flags.
1021Common flags: build-std, unstable-options")]
1022 pub cargo_z_flags: Vec<String>,
1023
1024 #[arg(long = "config", value_name = "KEY=VALUE",
1026 action = clap::ArgAction::Append, help_heading = "Additional Options",
1027 long_help = "\
1028Override a Cargo configuration value. The argument should be in TOML syntax of KEY=VALUE.
1029This flag may be specified multiple times.
1030Example: --config 'build.jobs=4' --config 'profile.release.lto=true'")]
1031 pub cargo_config: Vec<String>,
1032
1033 #[arg(short = 'C', long = "directory", env = "CARGO_CWD",
1035 value_name = "DIR", value_hint = ValueHint::DirPath,
1036 help_heading = "Additional Options",
1037 long_help = "\
1038Changes the current working directory before executing any specified operations.
1039This affects where cargo looks for the project manifest (Cargo.toml) and .cargo/config.toml.")]
1040 pub cargo_cwd: Option<PathBuf>,
1041
1042 #[arg(
1044 long = "toolchain",
1045 env = "TOOLCHAIN",
1046 value_name = "TOOLCHAIN",
1047 help_heading = "Additional Options",
1048 long_help = "\
1049Specify the Rust toolchain to use for compilation. This is an alternative to the +toolchain
1050syntax (e.g., +nightly). Examples: --toolchain nightly, --toolchain stable, --toolchain 1.75.0"
1051 )]
1052 pub toolchain_option: Option<String>,
1053
1054 #[arg(long, visible_alias = "github-proxy-mirror", env = "GH_PROXY", value_name = "URL",
1056 value_hint = ValueHint::Url, hide_env = true,
1057 help_heading = "Additional Options",
1058 long_help = "\
1059Specify a GitHub mirror/proxy URL for downloading cross-compiler toolchains.
1060Useful in regions where GitHub access is slow or restricted.
1061Example: --github-proxy 'https://ghproxy.com/'")]
1062 pub github_proxy: Option<String>,
1063
1064 #[arg(
1066 long,
1067 env = "CLEAN_CACHE",
1068 help_heading = "Additional Options",
1069 long_help = "\
1070Clean the target directory before building. Equivalent to running 'cargo clean' before the build."
1071 )]
1072 pub clean_cache: bool,
1073
1074 #[arg(
1076 long,
1077 env = "NO_APPEND_TARGET",
1078 help_heading = "Additional Options",
1079 long_help = "\
1080Disable automatic insertion of '--target <triple>' when using the 'exec' command
1081with a cargo invocation. By default, 'cargo cross exec --target ... -- cargo ...'
1082will add '--target <triple>' to the cargo command unless it is already present."
1083 )]
1084 pub no_append_target: bool,
1085
1086 #[arg(
1089 last = true,
1090 allow_hyphen_values = true,
1091 value_name = "ARGS",
1092 help = "Arguments passed through to the underlying cargo command",
1093 long_help = "\
1094Arguments passed through to the underlying cargo command. Everything after -- is passed
1095directly to cargo/test runner. For test command, these are passed to the test binary.
1096Examples: test -- --nocapture --test-threads=1, run -- --arg1 --arg2"
1097 )]
1098 pub passthrough_args: Vec<String>,
1099}
1100
1101impl BuildArgs {
1102 #[must_use]
1104 pub fn default_for_host() -> Self {
1105 Self {
1106 profile: "dev".to_string(),
1107 glibc_version: DEFAULT_GLIBC_VERSION.to_string(),
1108 iphone_sdk_version: DEFAULT_IPHONE_SDK_VERSION.to_string(),
1109 macos_sdk_version: DEFAULT_MACOS_SDK_VERSION.to_string(),
1110 freebsd_version: DEFAULT_FREEBSD_VERSION.to_string(),
1111 ndk_version: DEFAULT_NDK_VERSION.to_string(),
1112 qemu_version: DEFAULT_QEMU_VERSION.to_string(),
1113 cross_make_version: DEFAULT_CROSS_MAKE_VERSION.to_string(),
1114 ..Default::default()
1115 }
1116 }
1117}
1118
1119fn parse_optional_bool(s: &str) -> std::result::Result<bool, String> {
1121 match s.to_lowercase().as_str() {
1122 "true" | "1" | "yes" => Ok(true),
1123 "false" | "0" | "no" => Ok(false),
1124 _ => Err(format!("invalid bool value: {s}")),
1125 }
1126}
1127
1128fn parse_build_std(s: &str) -> std::result::Result<String, String> {
1130 match s.to_lowercase().as_str() {
1131 "false" | "0" | "no" | "" => Ok(String::new()), "true" | "1" | "yes" => Ok("true".to_string()),
1133 _ => Ok(s.to_string()),
1134 }
1135}
1136
1137#[derive(Debug, Clone, PartialEq, Eq)]
1139pub struct Command(String);
1140
1141impl Default for Command {
1142 fn default() -> Self {
1143 Self::build()
1144 }
1145}
1146
1147impl Command {
1148 #[must_use]
1149 pub fn new(name: impl Into<String>) -> Self {
1150 Self(name.into())
1151 }
1152
1153 #[must_use]
1154 pub fn build() -> Self {
1155 Self::new("build")
1156 }
1157
1158 #[must_use]
1159 pub fn check() -> Self {
1160 Self::new("check")
1161 }
1162
1163 #[must_use]
1164 pub fn run() -> Self {
1165 Self::new("run")
1166 }
1167
1168 #[must_use]
1169 pub fn test() -> Self {
1170 Self::new("test")
1171 }
1172
1173 #[must_use]
1174 pub fn bench() -> Self {
1175 Self::new("bench")
1176 }
1177
1178 #[must_use]
1179 pub fn clippy() -> Self {
1180 Self::new("clippy")
1181 }
1182
1183 #[must_use]
1184 pub fn setup() -> Self {
1185 Self::new("setup")
1186 }
1187
1188 #[must_use]
1189 pub fn exec() -> Self {
1190 Self::new("exec")
1191 }
1192
1193 #[must_use]
1194 pub fn as_str(&self) -> &str {
1195 &self.0
1196 }
1197
1198 #[must_use]
1199 pub fn needs_runner(&self) -> bool {
1200 matches!(self.as_str(), "run" | "test" | "bench")
1201 }
1202}
1203
1204#[derive(Debug, Clone)]
1206pub struct Args {
1207 pub toolchain: Option<String>,
1209 pub command: Command,
1211 pub targets: Vec<String>,
1213 pub no_cargo_target: bool,
1215 pub cross_make_version: String,
1217 pub cross_compiler_dir: PathBuf,
1219 pub build: BuildArgs,
1221}
1222
1223impl std::ops::Deref for Args {
1224 type Target = BuildArgs;
1225
1226 fn deref(&self) -> &Self::Target {
1227 &self.build
1228 }
1229}
1230
1231impl std::ops::DerefMut for Args {
1232 fn deref_mut(&mut self) -> &mut Self::Target {
1233 &mut self.build
1234 }
1235}
1236
1237impl Args {
1238 fn from_build_args(b: BuildArgs, command: Command, toolchain: Option<String>) -> Result<Self> {
1240 let cross_compiler_dir = b
1241 .cross_compiler_dir
1242 .clone()
1243 .unwrap_or_else(|| std::env::temp_dir().join("rust-cross-compiler"));
1244 let targets = expand_target_list(&b.targets)?;
1245
1246 Ok(Self {
1247 toolchain,
1248 command,
1249 targets,
1250 no_cargo_target: false,
1251 cross_make_version: b.cross_make_version.clone(),
1252 cross_compiler_dir,
1253 build: b,
1254 })
1255 }
1256}
1257
1258pub enum ParseResult {
1260 Build(Box<Args>),
1262 Setup(Box<SetupArgs>),
1264 Exec(Box<ExecArgs>),
1266 ShowTargets(OutputFormat),
1268 ShowVersion,
1270}
1271
1272fn sanitize_clap_env() {
1276 let empty_vars: Vec<_> = std::env::vars()
1277 .filter(|(_, v)| v.is_empty())
1278 .map(|(k, _)| k)
1279 .collect();
1280
1281 for var in empty_vars {
1282 std::env::remove_var(&var);
1283 }
1284}
1285
1286pub fn parse_args() -> Result<ParseResult> {
1288 let args: Vec<String> = std::env::args().collect();
1289 parse_args_from(args)
1290}
1291
1292#[derive(Parser, Debug)]
1293#[command(name = "cargo-cross")]
1294#[command(styles = cli_styles())]
1295struct ExternalCargoCli {
1296 #[command(flatten)]
1297 build: BuildArgs,
1298}
1299
1300pub fn parse_args_from(args: Vec<String>) -> Result<ParseResult> {
1302 use std::env;
1303
1304 sanitize_clap_env();
1307
1308 let mut toolchain: Option<String> = None;
1309
1310 let is_cargo_subcommand = env::var("CARGO").is_ok()
1314 && env::var("CARGO_HOME").is_ok()
1315 && args.get(1).map(String::as_str) == Some(SUBCOMMAND);
1316
1317 let skip_count = if is_cargo_subcommand { 2 } else { 1 };
1318 let mut args: Vec<String> = args.iter().skip(skip_count).cloned().collect();
1319
1320 if let Some(tc) = args.first().and_then(|a| a.strip_prefix('+')) {
1323 toolchain = Some(tc.to_string());
1324 args.remove(0);
1325 }
1326
1327 if let Some(command_name) = args.first().cloned() {
1328 let remaining_args: Vec<String> = args.iter().skip(1).cloned().collect();
1329 let help_or_version_requested = has_wrapper_help_or_version_request(&remaining_args);
1330
1331 if let Some(canonical_name) = canonical_cargo_command_name(&command_name) {
1332 if !help_or_version_requested {
1333 return parse_cargo_command_args(
1334 &command_name,
1335 canonical_name,
1336 remaining_args,
1337 toolchain,
1338 );
1339 }
1340 }
1341 if should_parse_as_external_cargo_command(&command_name) {
1342 return parse_cargo_command_args(
1343 &command_name,
1344 &command_name,
1345 remaining_args,
1346 toolchain,
1347 );
1348 }
1349 }
1350
1351 args.insert(0, BIN_NAME.to_string());
1353
1354 let cmd = build_command_with_dynamic_help();
1356
1357 let cli = match cmd.try_get_matches_from(&args) {
1359 Ok(matches) => {
1360 Cli::from_arg_matches(&matches).map_err(|e| CrossError::ClapError(e.to_string()))?
1361 }
1362 Err(e) => {
1363 if matches!(
1365 e.kind(),
1366 clap::error::ErrorKind::DisplayHelp
1367 | clap::error::ErrorKind::DisplayVersion
1368 | clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
1369 ) {
1370 e.exit();
1371 }
1372 return Err(CrossError::ClapError(e.render().to_string()));
1374 }
1375 };
1376
1377 process_cli(cli, toolchain)
1378}
1379
1380fn has_wrapper_help_or_version_request(args: &[String]) -> bool {
1381 args.iter()
1382 .take_while(|arg| arg.as_str() != "--")
1383 .any(|arg| matches!(arg.as_str(), "--help" | "-h" | "--version" | "-V"))
1384}
1385
1386fn build_command_with_dynamic_help() -> clap::Command {
1388 let prog = program_name();
1389
1390 let usage = format!("{prog} [+toolchain] <COMMAND> [OPTIONS]");
1392 let after_help = format!(
1393 "Use '{prog} <COMMAND> --help' for more information about a command.\n\n\
1394TOOLCHAIN:\n \
1395 If the first argument begins with +, it will be interpreted as a Rust toolchain\n \
1396 name (such as +nightly, +stable, or +1.75.0). This follows the same convention\n \
1397 as rustup and cargo.\n\n\
1398EXAMPLES:\n \
1399 {prog} build -t x86_64-unknown-linux-musl\n \
1400 {prog} +nightly build -t aarch64-unknown-linux-gnu --profile release\n \
1401 {prog} build -t '*-linux-musl' --crt-static true\n \
1402 {prog} test -t x86_64-unknown-linux-musl -- --nocapture"
1403 );
1404
1405 let glibc_help = format!(
1407 "Specify glibc version for GNU libc targets. The version determines the minimum Linux kernel\n\
1408 version required. Lower versions provide better compatibility with older systems.\n\
1409 Supported: {}",
1410 supported_glibc_versions_str()
1411 );
1412 let freebsd_help = format!(
1413 "Specify FreeBSD version for FreeBSD targets. Supported: {}",
1414 supported_freebsd_versions_str()
1415 );
1416 let iphone_sdk_help = format!(
1417 "Specify iPhone SDK version for iOS targets. On Linux: uses pre-built SDK from releases.\n\
1418 On macOS: uses installed Xcode SDK. Supported on Linux: {}",
1419 supported_iphone_sdk_versions_str()
1420 );
1421 let macos_sdk_help = format!(
1422 "Specify macOS SDK version for Darwin targets. On Linux: uses osxcross with pre-built SDK.\n\
1423 On macOS: uses installed Xcode SDK. Supported on Linux: {}",
1424 supported_macos_sdk_versions_str()
1425 );
1426
1427 let mut cmd = Cli::command().override_usage(usage).after_help(after_help);
1429
1430 for subcmd_name in &["build", "check", "run", "test", "bench", "clippy"] {
1432 let glibc_help = glibc_help.clone();
1433 let freebsd_help = freebsd_help.clone();
1434 let iphone_sdk_help = iphone_sdk_help.clone();
1435 let macos_sdk_help = macos_sdk_help.clone();
1436 cmd = cmd.mut_subcommand(*subcmd_name, |subcmd| {
1437 subcmd
1438 .override_usage(format!(
1439 "{prog} [+toolchain] {subcmd_name} [OPTIONS] [-- <PASSTHROUGH_ARGS>...]"
1440 ))
1441 .mut_arg("glibc_version", |arg| arg.long_help(glibc_help))
1442 .mut_arg("freebsd_version", |arg| arg.long_help(freebsd_help))
1443 .mut_arg("iphone_sdk_version", |arg| arg.long_help(iphone_sdk_help))
1444 .mut_arg("macos_sdk_version", |arg| arg.long_help(macos_sdk_help))
1445 });
1446 }
1447
1448 cmd
1449}
1450
1451fn should_parse_as_external_cargo_command(command_name: &str) -> bool {
1452 if command_name.starts_with('-') {
1453 return false;
1454 }
1455
1456 canonical_cargo_command_name(command_name).is_none()
1457 && is_supported_external_cargo_command(command_name)
1458}
1459
1460fn canonical_cargo_command_name(command_name: &str) -> Option<&'static str> {
1461 match command_name {
1462 "build" | "b" => Some("build"),
1463 "check" | "c" => Some("check"),
1464 "run" | "r" => Some("run"),
1465 "test" | "t" => Some("test"),
1466 "bench" => Some("bench"),
1467 "clippy" => Some("clippy"),
1468 _ => None,
1469 }
1470}
1471
1472fn is_supported_external_cargo_command(command_name: &str) -> bool {
1473 matches!(command_name, "doc" | "fix" | "rustc" | "rustdoc")
1474}
1475
1476fn parse_cargo_command_args(
1477 display_name: &str,
1478 canonical_name: &str,
1479 args: Vec<String>,
1480 toolchain: Option<String>,
1481) -> Result<ParseResult> {
1482 let processed_args = preprocess_cargo_args(args);
1483 let mut clap_args = Vec::with_capacity(processed_args.len() + 1);
1484 clap_args.push(BIN_NAME.to_string());
1485 clap_args.extend(processed_args);
1486
1487 let cmd = build_external_cargo_command_with_dynamic_help(display_name);
1488 let cli = match cmd.try_get_matches_from(&clap_args) {
1489 Ok(matches) => ExternalCargoCli::from_arg_matches(&matches)
1490 .map_err(|e| CrossError::ClapError(e.to_string()))?,
1491 Err(e) => {
1492 if matches!(
1493 e.kind(),
1494 clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion
1495 ) {
1496 e.exit();
1497 }
1498 return Err(CrossError::ClapError(e.render().to_string()));
1499 }
1500 };
1501
1502 let args = finalize_args(cli.build, Command::new(canonical_name), toolchain)?;
1503 Ok(ParseResult::Build(Box::new(args)))
1504}
1505
1506#[derive(Clone, Copy, Debug)]
1507struct KnownArgSpec {
1508 takes_value: bool,
1509 min_values: usize,
1510 max_values: usize,
1511 allow_hyphen_values: bool,
1512}
1513
1514fn preprocess_cargo_args(args: Vec<String>) -> Vec<String> {
1515 let parser = ExternalCargoCli::command();
1516 let (long_args, short_args) = known_arg_maps(&parser);
1517 let mut processed = Vec::with_capacity(args.len());
1518 let mut pending_value: Option<KnownArgSpec> = None;
1519 let mut passthrough = false;
1520 let mut index = 0;
1521
1522 while index < args.len() {
1523 let token = &args[index];
1524
1525 if passthrough {
1526 processed.push(token.clone());
1527 index += 1;
1528 continue;
1529 }
1530
1531 if token == "--" {
1532 pending_value = None;
1533 passthrough = true;
1534 processed.push(token.clone());
1535 index += 1;
1536 continue;
1537 }
1538
1539 if matches!(token.as_str(), "--help" | "-h" | "--version" | "-V") {
1540 pending_value = None;
1541 processed.push(token.clone());
1542 index += 1;
1543 continue;
1544 }
1545
1546 if let Some(spec) = pending_value {
1547 if should_consume_known_value(token, spec) {
1548 processed.push(token.clone());
1549 pending_value = None;
1550 index += 1;
1551 continue;
1552 }
1553 pending_value = None;
1554 }
1555
1556 if let Some(spec) = classify_known_long_arg(token, &long_args) {
1557 processed.push(token.clone());
1558 pending_value = needs_following_value(token, spec);
1559 index += 1;
1560 continue;
1561 }
1562
1563 if let Some(spec) = classify_known_short_arg(token, &short_args) {
1564 processed.push(token.clone());
1565 pending_value = needs_following_value(token, spec);
1566 index += 1;
1567 continue;
1568 }
1569
1570 push_cargo_arg(&mut processed, token);
1571 if should_pair_unknown_value(token, args.get(index + 1).map(String::as_str)) {
1572 if let Some(value) = args.get(index + 1) {
1573 push_cargo_arg(&mut processed, value);
1574 index += 1;
1575 }
1576 }
1577 index += 1;
1578 }
1579
1580 processed
1581}
1582
1583fn known_arg_maps(
1584 command: &clap::Command,
1585) -> (HashMap<String, KnownArgSpec>, HashMap<char, KnownArgSpec>) {
1586 let mut long_args = HashMap::new();
1587 let mut short_args = HashMap::new();
1588
1589 for arg in command.get_arguments() {
1590 if arg.is_positional() {
1591 continue;
1592 }
1593
1594 let (takes_value, min_values, max_values) = infer_arg_value_shape(arg);
1595 let spec = KnownArgSpec {
1596 takes_value,
1597 min_values,
1598 max_values,
1599 allow_hyphen_values: arg.is_allow_hyphen_values_set(),
1600 };
1601
1602 if let Some(long) = arg.get_long() {
1603 long_args.insert(long.to_string(), spec);
1604 }
1605 if let Some(aliases) = arg.get_long_and_visible_aliases() {
1606 for alias in aliases {
1607 long_args.insert(alias.to_string(), spec);
1608 }
1609 }
1610 if let Some(short) = arg.get_short() {
1611 short_args.insert(short, spec);
1612 }
1613 if let Some(aliases) = arg.get_short_and_visible_aliases() {
1614 for alias in aliases {
1615 short_args.insert(alias, spec);
1616 }
1617 }
1618 }
1619
1620 (long_args, short_args)
1621}
1622
1623fn infer_arg_value_shape(arg: &clap::Arg) -> (bool, usize, usize) {
1624 if let Some(range) = arg.get_num_args() {
1625 return (range.takes_values(), range.min_values(), range.max_values());
1626 }
1627
1628 match arg.get_action() {
1629 ArgAction::Set | ArgAction::Append => (true, 1, 1),
1630 ArgAction::SetTrue
1631 | ArgAction::SetFalse
1632 | ArgAction::Count
1633 | ArgAction::Help
1634 | ArgAction::HelpShort
1635 | ArgAction::HelpLong
1636 | ArgAction::Version => (false, 0, 0),
1637 _ => (false, 0, 0),
1638 }
1639}
1640
1641fn classify_known_long_arg(
1642 token: &str,
1643 long_args: &HashMap<String, KnownArgSpec>,
1644) -> Option<KnownArgSpec> {
1645 if !token.starts_with("--") || token == "--" {
1646 return None;
1647 }
1648
1649 let name = token
1650 .trim_start_matches("--")
1651 .split_once('=')
1652 .map_or_else(|| token.trim_start_matches("--"), |(name, _)| name);
1653 long_args.get(name).copied()
1654}
1655
1656fn classify_known_short_arg(
1657 token: &str,
1658 short_args: &HashMap<char, KnownArgSpec>,
1659) -> Option<KnownArgSpec> {
1660 if !token.starts_with('-') || token.starts_with("--") || token == "-" {
1661 return None;
1662 }
1663
1664 let rest = token.strip_prefix('-')?;
1665 let mut chars = rest.chars();
1666 let first = chars.next()?;
1667 let spec = short_args.get(&first).copied()?;
1668
1669 if spec.takes_value {
1670 return Some(spec);
1671 }
1672
1673 if rest
1674 .chars()
1675 .all(|short| short_args.get(&short).is_some_and(|arg| !arg.takes_value))
1676 {
1677 return Some(spec);
1678 }
1679
1680 None
1681}
1682
1683fn needs_following_value(token: &str, spec: KnownArgSpec) -> Option<KnownArgSpec> {
1684 if !spec.takes_value || spec.max_values == 0 {
1685 return None;
1686 }
1687
1688 let has_inline_value = if token.starts_with("--") {
1689 token.contains('=')
1690 } else {
1691 token.len() > 2
1692 };
1693
1694 if has_inline_value {
1695 None
1696 } else {
1697 Some(spec)
1698 }
1699}
1700
1701fn should_consume_known_value(token: &str, spec: KnownArgSpec) -> bool {
1702 if token == "--" {
1703 return false;
1704 }
1705
1706 if spec.min_values == 0 && token.starts_with('-') && !spec.allow_hyphen_values {
1707 return false;
1708 }
1709
1710 !token.starts_with('-') || spec.allow_hyphen_values || spec.min_values > 0
1711}
1712
1713fn should_pair_unknown_value(current: &str, next: Option<&str>) -> bool {
1714 if current == "-" || current == "--" {
1715 return false;
1716 }
1717
1718 if !(current.starts_with("--") || (current.starts_with('-') && current.len() == 2)) {
1719 return false;
1720 }
1721
1722 let Some(next) = next else {
1723 return false;
1724 };
1725
1726 !next.starts_with('-')
1727}
1728
1729fn push_cargo_arg(processed: &mut Vec<String>, value: &str) {
1730 processed.push("--cargo-args".to_string());
1731 processed.push(value.to_string());
1732}
1733
1734fn build_external_cargo_command_with_dynamic_help(command_name: &str) -> clap::Command {
1735 let prog = program_name();
1736 let after_help = format!(
1737 "This command forwards to 'cargo {command_name}' after configuring the\n\
1738cross-compilation environment.\n\n\
1739EXAMPLES:\n \
1740{prog} {command_name} -t x86_64-unknown-linux-musl\n \
1741{prog} {command_name} -t aarch64-unknown-linux-gnu -- --help"
1742 );
1743
1744 ExternalCargoCli::command()
1745 .override_usage(format!(
1746 "{prog} [+toolchain] {command_name} [OPTIONS] [-- <PASSTHROUGH_ARGS>...]"
1747 ))
1748 .after_help(after_help)
1749}
1750
1751fn process_cli(cli: Cli, toolchain: Option<String>) -> Result<ParseResult> {
1752 match cli.command {
1753 CliCommand::Build(args) => {
1754 let args = finalize_args(args, Command::build(), toolchain)?;
1755 Ok(ParseResult::Build(Box::new(args)))
1756 }
1757 CliCommand::Check(args) => {
1758 let args = finalize_args(args, Command::check(), toolchain)?;
1759 Ok(ParseResult::Build(Box::new(args)))
1760 }
1761 CliCommand::Run(args) => {
1762 let args = finalize_args(args, Command::run(), toolchain)?;
1763 Ok(ParseResult::Build(Box::new(args)))
1764 }
1765 CliCommand::Test(args) => {
1766 let args = finalize_args(args, Command::test(), toolchain)?;
1767 Ok(ParseResult::Build(Box::new(args)))
1768 }
1769 CliCommand::Bench(args) => {
1770 let args = finalize_args(args, Command::bench(), toolchain)?;
1771 Ok(ParseResult::Build(Box::new(args)))
1772 }
1773 CliCommand::Clippy(args) => {
1774 let args = finalize_args(args, Command::clippy(), toolchain)?;
1775 Ok(ParseResult::Build(Box::new(args)))
1776 }
1777 CliCommand::Setup(setup) => {
1778 let args = finalize_args(setup.build, Command::setup(), toolchain)?;
1779 Ok(ParseResult::Setup(Box::new(SetupArgs {
1780 args,
1781 format: setup.format,
1782 })))
1783 }
1784 CliCommand::Exec(exec) => {
1785 let mut build = exec.build;
1786 populate_env_arg_fallbacks(&mut build);
1787 let command = std::mem::take(&mut build.passthrough_args);
1788 if command.is_empty() {
1789 return Err(CrossError::InvalidArgument(
1790 "exec requires a command after `--`".to_string(),
1791 ));
1792 }
1793 let args = finalize_args(build, Command::exec(), toolchain)?;
1794 Ok(ParseResult::Exec(Box::new(ExecArgs { args, command })))
1795 }
1796 CliCommand::Targets(args) => Ok(ParseResult::ShowTargets(args.format)),
1797 CliCommand::Version => Ok(ParseResult::ShowVersion),
1798 }
1799}
1800
1801fn is_glob_pattern(s: &str) -> bool {
1803 s.contains('*') || s.contains('?') || s.contains('[')
1804}
1805
1806fn validate_target_triple(target: &str) -> Result<()> {
1808 for c in target.chars() {
1809 if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '-' && c != '_' {
1810 return Err(CrossError::InvalidTargetTriple {
1811 target: target.to_string(),
1812 char: c,
1813 });
1814 }
1815 }
1816 Ok(())
1817}
1818
1819fn expand_target_list(targets: &[String]) -> Result<Vec<String>> {
1821 let mut result = Vec::new();
1822 for target in targets {
1823 for part in target.split([',', '\n']) {
1825 let part = part.trim();
1826 if part.is_empty() {
1827 continue;
1828 }
1829 let expanded = config::expand_targets(part);
1830 if expanded.is_empty() {
1831 if is_glob_pattern(part) {
1833 return Err(CrossError::NoMatchingTargets {
1834 pattern: part.to_string(),
1835 });
1836 }
1837 validate_target_triple(part)?;
1839 if !result.contains(&part.to_string()) {
1840 result.push(part.to_string());
1841 }
1842 } else {
1843 for t in expanded {
1844 let t = t.to_string();
1845 if !result.contains(&t) {
1846 result.push(t);
1847 }
1848 }
1849 }
1850 }
1851 }
1852 Ok(result)
1853}
1854
1855fn finalize_args(
1856 mut build_args: BuildArgs,
1857 command: Command,
1858 toolchain: Option<String>,
1859) -> Result<Args> {
1860 if build_args.release {
1862 build_args.profile = "release".to_string();
1863 }
1864
1865 if build_args
1867 .build_std
1868 .as_ref()
1869 .is_some_and(std::string::String::is_empty)
1870 {
1871 build_args.build_std = None;
1872 }
1873
1874 populate_env_arg_fallbacks(&mut build_args);
1875
1876 let final_toolchain = toolchain.or_else(|| build_args.toolchain_option.clone());
1878
1879 let mut args = Args::from_build_args(build_args, command, final_toolchain)?;
1880
1881 validate_versions(&args)?;
1883
1884 if args.targets.is_empty() {
1886 let host = config::HostPlatform::detect();
1887 args.targets.push(host.triple);
1888 args.no_toolchain_setup = true;
1889 args.no_cargo_target = true;
1890 }
1891 Ok(args)
1894}
1895
1896fn populate_env_arg_fallbacks(build_args: &mut BuildArgs) {
1897 if build_args.cargo_args.is_empty() {
1898 if let Some(env_args) = parse_env_args("CARGO_ARGS") {
1899 build_args.cargo_args = env_args;
1900 }
1901 }
1902 if build_args.passthrough_args.is_empty() {
1903 if let Some(env_args) = parse_passthrough_env_args("CARGO_PASSTHROUGH_ARGS") {
1904 build_args.passthrough_args = env_args;
1905 }
1906 }
1907}
1908
1909fn parse_passthrough_env_args(env_name: &str) -> Option<Vec<String>> {
1910 let mut args = parse_env_args(env_name)?;
1911 if args.first().is_some_and(|arg| arg == "--") {
1912 args.remove(0);
1913 }
1914 if args.is_empty() {
1915 None
1916 } else {
1917 Some(args)
1918 }
1919}
1920
1921fn parse_env_args(env_name: &str) -> Option<Vec<String>> {
1924 let value = std::env::var(env_name).ok()?;
1925 let value = value.trim();
1926 if value.is_empty() {
1927 return None;
1928 }
1929 match shlex::split(value) {
1930 Some(parsed) if !parsed.is_empty() => Some(parsed),
1931 Some(_) => None,
1932 None => {
1933 eprintln!("Warning: Failed to parse {env_name} (mismatched quotes?): {value}");
1934 None
1935 }
1936 }
1937}
1938
1939fn validate_versions(args: &Args) -> Result<()> {
1941 if !args.glibc_version.is_empty()
1944 && !SUPPORTED_GLIBC_VERSIONS.contains(&args.glibc_version.as_str())
1945 {
1946 return Err(CrossError::UnsupportedGlibcVersion {
1947 version: args.glibc_version.clone(),
1948 supported: SUPPORTED_GLIBC_VERSIONS.join(", "),
1949 });
1950 }
1951
1952 let host = config::HostPlatform::detect();
1953 if !host.is_darwin()
1954 && !SUPPORTED_IPHONE_SDK_VERSIONS.contains(&args.iphone_sdk_version.as_str())
1955 {
1956 return Err(CrossError::UnsupportedIphoneSdkVersion {
1957 version: args.iphone_sdk_version.clone(),
1958 supported: SUPPORTED_IPHONE_SDK_VERSIONS.join(", "),
1959 });
1960 }
1961
1962 if !host.is_darwin() && !SUPPORTED_MACOS_SDK_VERSIONS.contains(&args.macos_sdk_version.as_str())
1963 {
1964 return Err(CrossError::UnsupportedMacosSdkVersion {
1965 version: args.macos_sdk_version.clone(),
1966 supported: SUPPORTED_MACOS_SDK_VERSIONS.join(", "),
1967 });
1968 }
1969
1970 if !SUPPORTED_FREEBSD_VERSIONS.contains(&args.freebsd_version.as_str()) {
1971 return Err(CrossError::UnsupportedFreebsdVersion {
1972 version: args.freebsd_version.clone(),
1973 supported: SUPPORTED_FREEBSD_VERSIONS.join(", "),
1974 });
1975 }
1976
1977 Ok(())
1978}
1979
1980pub fn print_all_targets(format: OutputFormat) {
1982 let mut targets: Vec<_> = config::all_targets().collect();
1983 targets.sort_unstable();
1984
1985 match format {
1986 OutputFormat::Text => {
1987 use colored::Colorize;
1988 println!("{}", "Supported Rust targets:".bright_green());
1989 for target in &targets {
1990 println!(" {}", target.bright_cyan());
1991 }
1992 }
1993 OutputFormat::Json => {
1994 let json_array = serde_json::to_string(&targets).unwrap_or_else(|_| "[]".to_string());
1995 println!("{json_array}");
1996 }
1997 OutputFormat::Plain => {
1998 for target in &targets {
1999 println!("{target}");
2000 }
2001 }
2002 }
2003
2004 if let Ok(github_output) = std::env::var("GITHUB_OUTPUT") {
2006 let json_array = serde_json::to_string(&targets).unwrap_or_else(|_| "[]".to_string());
2007 if let Ok(mut file) = std::fs::OpenOptions::new()
2008 .append(true)
2009 .open(&github_output)
2010 {
2011 use std::io::Write;
2012 let _ = writeln!(file, "all-targets={json_array}");
2013 }
2014 }
2015}
2016
2017pub fn print_version() {
2019 use colored::Colorize;
2020
2021 let version = env!("CARGO_PKG_VERSION");
2022 let name = env!("CARGO_PKG_NAME");
2023 println!("{} {}", name.bright_green(), version.bright_cyan());
2024}
2025
2026#[cfg(test)]
2027mod tests {
2028 use super::*;
2029
2030 fn parse(args: &[&str]) -> Result<Args> {
2031 let args: Vec<String> = args.iter().map(std::string::ToString::to_string).collect();
2032 match parse_args_from(args)? {
2033 ParseResult::Build(args) => Ok(*args),
2034 ParseResult::ShowTargets(_) => panic!("unexpected ShowTargets"),
2035 ParseResult::Setup(_) => panic!("unexpected Setup"),
2036 ParseResult::Exec(_) => panic!("unexpected Exec"),
2037 ParseResult::ShowVersion => panic!("unexpected ShowVersion"),
2038 }
2039 }
2040
2041 fn parse_setup(args: &[&str]) -> Result<SetupArgs> {
2042 let args: Vec<String> = args.iter().map(std::string::ToString::to_string).collect();
2043 match parse_args_from(args)? {
2044 ParseResult::Setup(args) => Ok(*args),
2045 _ => panic!("unexpected parse result"),
2046 }
2047 }
2048
2049 fn parse_exec(args: &[&str]) -> Result<ExecArgs> {
2050 let args: Vec<String> = args.iter().map(std::string::ToString::to_string).collect();
2051 match parse_args_from(args)? {
2052 ParseResult::Exec(args) => Ok(*args),
2053 _ => panic!("unexpected parse result"),
2054 }
2055 }
2056
2057 #[test]
2061 fn test_parse_build_command() {
2062 let args = parse(&["cargo-cross", "build"]).unwrap();
2063 assert_eq!(args.command, Command::build());
2064 assert_eq!(args.profile, "dev");
2065 }
2066
2067 #[test]
2068 fn test_parse_check_command() {
2069 let args = parse(&["cargo-cross", "check"]).unwrap();
2070 assert_eq!(args.command, Command::check());
2071 }
2072
2073 #[test]
2074 fn test_parse_clippy_command() {
2075 let args = parse(&["cargo-cross", "clippy"]).unwrap();
2076 assert_eq!(args.command, Command::clippy());
2077 }
2078
2079 #[test]
2080 fn test_parse_clippy_unknown_cargo_flags() {
2081 let args = parse(&[
2082 "cargo-cross",
2083 "clippy",
2084 "--workspace",
2085 "--all-targets",
2086 "--fix",
2087 "--allow-dirty",
2088 "--target",
2089 "x86_64-pc-windows-msvc",
2090 ])
2091 .unwrap();
2092 assert!(args.workspace);
2093 assert!(args.build_all_targets);
2094 assert_eq!(
2095 args.cargo_args,
2096 vec!["--fix".to_string(), "--allow-dirty".to_string()]
2097 );
2098 assert_eq!(args.targets, vec!["x86_64-pc-windows-msvc"]);
2099 }
2100
2101 #[test]
2102 fn test_parse_external_cargo_command() {
2103 let args = parse(&["cargo-cross", "doc"]).unwrap();
2104 assert_eq!(args.command, Command::new("doc"));
2105 }
2106
2107 #[test]
2108 fn test_reject_non_build_like_external_cargo_command() {
2109 let result = parse(&["cargo-cross", "metadata"]);
2110 assert!(result.is_err());
2111 }
2112
2113 #[test]
2114 fn test_parse_external_command_unknown_cargo_flags() {
2115 let args = parse(&[
2116 "cargo-cross",
2117 "doc",
2118 "--workspace",
2119 "--open",
2120 "--target",
2121 "x86_64-unknown-linux-musl",
2122 ])
2123 .unwrap();
2124 assert_eq!(args.command, Command::new("doc"));
2125 assert!(args.workspace);
2126 assert_eq!(args.cargo_args, vec!["--open".to_string()]);
2127 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2128 }
2129
2130 #[test]
2131 fn test_parse_setup_command() {
2132 let args =
2133 parse_setup(&["cargo-cross", "setup", "-t", "x86_64-unknown-linux-musl"]).unwrap();
2134 assert_eq!(args.args.command, Command::setup());
2135 assert_eq!(args.format, SetupOutputFormat::Auto);
2136 }
2137
2138 #[test]
2139 fn test_parse_setup_command_explicit_format() {
2140 let args = parse_setup(&[
2141 "cargo-cross",
2142 "setup",
2143 "-t",
2144 "x86_64-unknown-linux-musl",
2145 "--format",
2146 "fish",
2147 ])
2148 .unwrap();
2149 assert_eq!(args.format, SetupOutputFormat::Fish);
2150 }
2151
2152 #[test]
2153 fn test_parse_exec_command() {
2154 let args = parse_exec(&[
2155 "cargo-cross",
2156 "exec",
2157 "-t",
2158 "x86_64-unknown-linux-musl",
2159 "--",
2160 "env",
2161 "FOO=bar",
2162 ])
2163 .unwrap();
2164 assert_eq!(args.args.command, Command::exec());
2165 assert_eq!(args.command, vec!["env", "FOO=bar"]);
2166 }
2167
2168 #[test]
2169 fn test_parse_exec_command_disable_auto_target() {
2170 let args = parse_exec(&[
2171 "cargo-cross",
2172 "exec",
2173 "--no-append-target",
2174 "-t",
2175 "x86_64-unknown-linux-musl",
2176 "--",
2177 "cargo",
2178 "clippy",
2179 ])
2180 .unwrap();
2181 assert!(args.args.no_append_target);
2182 assert_eq!(args.command, vec!["cargo", "clippy"]);
2183 }
2184
2185 #[test]
2186 fn test_parse_exec_command_from_env_passthrough() {
2187 std::env::set_var("CARGO_PASSTHROUGH_ARGS", "-- env FOO=bar");
2188 let args = parse_exec(&["cargo-cross", "exec", "-t", "x86_64-unknown-linux-musl"]).unwrap();
2189 assert_eq!(args.args.command, Command::exec());
2190 assert_eq!(args.command, vec!["env", "FOO=bar"]);
2191 std::env::remove_var("CARGO_PASSTHROUGH_ARGS");
2192 }
2193
2194 #[test]
2195 fn test_parse_target() {
2196 let args = parse(&["cargo-cross", "build", "-t", "x86_64-unknown-linux-musl"]).unwrap();
2197 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2198 }
2199
2200 #[test]
2201 fn test_parse_multiple_targets() {
2202 let args = parse(&[
2203 "cargo-cross",
2204 "build",
2205 "-t",
2206 "x86_64-unknown-linux-musl,aarch64-unknown-linux-musl",
2207 ])
2208 .unwrap();
2209 assert_eq!(
2210 args.targets,
2211 vec!["x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl"]
2212 );
2213 }
2214
2215 #[test]
2216 fn test_parse_verbose() {
2217 let args = parse(&["cargo-cross", "build", "-vvv"]).unwrap();
2218 assert_eq!(args.verbose_level, 3);
2219 }
2220
2221 #[test]
2222 fn test_parse_crt_static_flag() {
2223 let args = parse(&["cargo-cross", "build", "--crt-static", "true"]).unwrap();
2224 assert_eq!(args.crt_static, Some(true));
2225 }
2226
2227 #[test]
2228 fn test_parse_crt_static_false() {
2229 let args = parse(&["cargo-cross", "build", "--crt-static", "false"]).unwrap();
2230 assert_eq!(args.crt_static, Some(false));
2231 }
2232
2233 #[test]
2234 fn test_parse_crt_static_no_value() {
2235 let args = parse(&["cargo-cross", "build", "--crt-static"]).unwrap();
2237 assert_eq!(args.crt_static, Some(true));
2238 }
2239
2240 #[test]
2241 fn test_parse_crt_static_not_provided() {
2242 let args = parse(&["cargo-cross", "build"]).unwrap();
2244 assert_eq!(args.crt_static, None);
2245 }
2246
2247 #[test]
2248 fn test_parse_build_std() {
2249 let args = parse(&["cargo-cross", "build", "--build-std", "true"]).unwrap();
2250 assert_eq!(args.build_std, Some("true".to_string()));
2251 }
2252
2253 #[test]
2254 fn test_parse_build_std_crates() {
2255 let args = parse(&["cargo-cross", "build", "--build-std", "core,alloc"]).unwrap();
2256 assert_eq!(args.build_std, Some("core,alloc".to_string()));
2257 }
2258
2259 #[test]
2260 fn test_parse_build_std_false() {
2261 let args = parse(&["cargo-cross", "build", "--build-std", "false"]).unwrap();
2262 assert_eq!(args.build_std, None);
2263 }
2264
2265 #[test]
2266 fn test_parse_build_std_no_value() {
2267 let args = parse(&["cargo-cross", "build", "--build-std"]).unwrap();
2269 assert_eq!(args.build_std, Some("true".to_string()));
2270 }
2271
2272 #[test]
2273 fn test_parse_features() {
2274 let args = parse(&["cargo-cross", "build", "--features", "foo,bar"]).unwrap();
2275 assert_eq!(args.features, Some("foo,bar".to_string()));
2276 }
2277
2278 #[test]
2279 fn test_parse_no_default_features() {
2280 let args = parse(&["cargo-cross", "build", "--no-default-features"]).unwrap();
2281 assert!(args.no_default_features);
2282 }
2283
2284 #[test]
2285 fn test_parse_profile() {
2286 let args = parse(&["cargo-cross", "build", "--profile", "dev"]).unwrap();
2287 assert_eq!(args.profile, "dev");
2288 }
2289
2290 #[test]
2291 fn test_parse_jobs() {
2292 let args = parse(&["cargo-cross", "build", "-j", "4"]).unwrap();
2293 assert_eq!(args.jobs, Some("4".to_string()));
2294 }
2295
2296 #[test]
2297 fn test_parse_passthrough_args() {
2298 let args = parse(&["cargo-cross", "build", "--", "--foo", "--bar"]).unwrap();
2299 assert_eq!(args.passthrough_args, vec!["--foo", "--bar"]);
2300 }
2301
2302 #[test]
2303 fn test_parse_passthrough_args_from_env_with_legacy_separator() {
2304 std::env::set_var("CARGO_PASSTHROUGH_ARGS", "-- --foo --bar");
2305 let args = parse(&["cargo-cross", "build"]).unwrap();
2306 assert_eq!(args.passthrough_args, vec!["--foo", "--bar"]);
2307 std::env::remove_var("CARGO_PASSTHROUGH_ARGS");
2308 }
2309
2310 #[test]
2311 fn test_parse_z_flag() {
2312 let args = parse(&["cargo-cross", "build", "-Z", "build-std"]).unwrap();
2313 assert_eq!(args.cargo_z_flags, vec!["build-std"]);
2314 }
2315
2316 #[test]
2317 fn test_parse_config_flag() {
2318 let args = parse(&["cargo-cross", "build", "--config", "opt-level=3"]).unwrap();
2319 assert_eq!(args.cargo_config, vec!["opt-level=3"]);
2320 }
2321
2322 #[test]
2323 fn test_targets_subcommand() {
2324 let args: Vec<String> = vec!["cargo-cross".to_string(), "targets".to_string()];
2325 match parse_args_from(args).unwrap() {
2326 ParseResult::ShowTargets(format) => {
2327 assert_eq!(format, OutputFormat::Text);
2328 }
2329 _ => panic!("expected ShowTargets"),
2330 }
2331 }
2332
2333 #[test]
2334 fn test_targets_json_format() {
2335 let args: Vec<String> = vec![
2336 "cargo-cross".to_string(),
2337 "targets".to_string(),
2338 "--format".to_string(),
2339 "json".to_string(),
2340 ];
2341 match parse_args_from(args).unwrap() {
2342 ParseResult::ShowTargets(format) => {
2343 assert_eq!(format, OutputFormat::Json);
2344 }
2345 _ => panic!("expected ShowTargets"),
2346 }
2347 }
2348
2349 #[test]
2350 fn test_targets_plain_format() {
2351 let args: Vec<String> = vec![
2352 "cargo-cross".to_string(),
2353 "targets".to_string(),
2354 "-f".to_string(),
2355 "plain".to_string(),
2356 ];
2357 match parse_args_from(args).unwrap() {
2358 ParseResult::ShowTargets(format) => {
2359 assert_eq!(format, OutputFormat::Plain);
2360 }
2361 _ => panic!("expected ShowTargets"),
2362 }
2363 }
2364
2365 #[test]
2366 fn test_parse_toolchain() {
2367 let args = parse(&["cargo-cross", "+nightly", "build"]).unwrap();
2368 assert_eq!(args.toolchain, Some("nightly".to_string()));
2369 assert_eq!(args.command, Command::build());
2370 }
2371
2372 #[test]
2373 fn test_parse_toolchain_with_target() {
2374 let args = parse(&[
2375 "cargo-cross",
2376 "+nightly",
2377 "build",
2378 "-t",
2379 "x86_64-unknown-linux-musl",
2380 ])
2381 .unwrap();
2382 assert_eq!(args.toolchain, Some("nightly".to_string()));
2383 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2384 }
2385
2386 #[test]
2389 fn test_equals_syntax_target() {
2390 let args = parse(&["cargo-cross", "build", "-t=x86_64-unknown-linux-musl"]).unwrap();
2391 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2392 }
2393
2394 #[test]
2395 fn test_equals_syntax_long_target() {
2396 let args = parse(&["cargo-cross", "build", "--target=x86_64-unknown-linux-musl"]).unwrap();
2397 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2398 }
2399
2400 #[test]
2401 fn test_equals_syntax_profile() {
2402 let args = parse(&["cargo-cross", "build", "--profile=dev"]).unwrap();
2403 assert_eq!(args.profile, "dev");
2404 }
2405
2406 #[test]
2407 fn test_equals_syntax_features() {
2408 let args = parse(&["cargo-cross", "build", "--features=foo,bar"]).unwrap();
2409 assert_eq!(args.features, Some("foo,bar".to_string()));
2410 }
2411
2412 #[test]
2413 fn test_equals_syntax_short_features() {
2414 let args = parse(&["cargo-cross", "build", "-F=foo,bar"]).unwrap();
2415 assert_eq!(args.features, Some("foo,bar".to_string()));
2416 }
2417
2418 #[test]
2419 fn test_equals_syntax_jobs() {
2420 let args = parse(&["cargo-cross", "build", "-j=8"]).unwrap();
2421 assert_eq!(args.jobs, Some("8".to_string()));
2422 }
2423
2424 #[test]
2425 fn test_equals_syntax_crt_static() {
2426 let args = parse(&["cargo-cross", "build", "--crt-static=true"]).unwrap();
2427 assert_eq!(args.crt_static, Some(true));
2428 }
2429
2430 #[test]
2431 fn test_equals_syntax_build_std() {
2432 let args = parse(&["cargo-cross", "build", "--build-std=core,alloc"]).unwrap();
2433 assert_eq!(args.build_std, Some("core,alloc".to_string()));
2434 }
2435
2436 #[test]
2437 fn test_equals_syntax_manifest_path() {
2438 let args = parse(&[
2439 "cargo-cross",
2440 "build",
2441 "--manifest-path=/path/to/Cargo.toml",
2442 ])
2443 .unwrap();
2444 assert_eq!(
2445 args.manifest_path,
2446 Some(PathBuf::from("/path/to/Cargo.toml"))
2447 );
2448 }
2449
2450 #[test]
2451 fn test_equals_syntax_cross_compiler_dir() {
2452 let args = parse(&["cargo-cross", "build", "--cross-compiler-dir=/opt/cross"]).unwrap();
2453 assert_eq!(args.cross_compiler_dir, PathBuf::from("/opt/cross"));
2454 }
2455
2456 #[test]
2457 fn test_equals_syntax_glibc_version() {
2458 let args = parse(&["cargo-cross", "build", "--glibc-version=2.31"]).unwrap();
2459 assert_eq!(args.glibc_version, "2.31");
2460 }
2461
2462 #[test]
2463 fn test_equals_syntax_cc_cxx_ar() {
2464 let args = parse(&[
2465 "cargo-cross",
2466 "build",
2467 "--cc=/usr/bin/gcc",
2468 "--cxx=/usr/bin/g++",
2469 "--ar=/usr/bin/ar",
2470 ])
2471 .unwrap();
2472 assert_eq!(args.cc, Some(PathBuf::from("/usr/bin/gcc")));
2473 assert_eq!(args.cxx, Some(PathBuf::from("/usr/bin/g++")));
2474 assert_eq!(args.ar, Some(PathBuf::from("/usr/bin/ar")));
2475 }
2476
2477 #[test]
2478 fn test_equals_syntax_linker() {
2479 let args = parse(&["cargo-cross", "build", "--linker=/usr/bin/ld.lld"]).unwrap();
2480 assert_eq!(args.linker, Some(PathBuf::from("/usr/bin/ld.lld")));
2481 }
2482
2483 #[test]
2484 fn test_equals_syntax_cflags_with_spaces() {
2485 let args = parse(&["cargo-cross", "build", "--cflags=-O2 -Wall -Wextra"]).unwrap();
2486 assert_eq!(args.cflags, Some("-O2 -Wall -Wextra".to_string()));
2487 }
2488
2489 #[test]
2490 fn test_equals_syntax_ldflags() {
2491 let args = parse(&["cargo-cross", "build", "--ldflags=-L/usr/local/lib -static"]).unwrap();
2492 assert_eq!(args.ldflags, Some("-L/usr/local/lib -static".to_string()));
2493 }
2494
2495 #[test]
2496 fn test_equals_syntax_rustflag() {
2497 let args = parse(&["cargo-cross", "build", "--rustflag=-C opt-level=3"]).unwrap();
2498 assert_eq!(args.rustflags, vec!["-C opt-level=3"]);
2499 }
2500
2501 #[test]
2502 fn test_equals_syntax_github_proxy() {
2503 let args = parse(&[
2504 "cargo-cross",
2505 "build",
2506 "--github-proxy=https://mirror.example.com/",
2507 ])
2508 .unwrap();
2509 assert_eq!(
2510 args.github_proxy,
2511 Some("https://mirror.example.com/".to_string())
2512 );
2513 }
2514
2515 #[test]
2516 fn test_equals_syntax_sccache_options() {
2517 let args = parse(&[
2518 "cargo-cross",
2519 "build",
2520 "--enable-sccache",
2521 "--sccache-dir=/tmp/sccache",
2522 "--sccache-cache-size=20G",
2523 ])
2524 .unwrap();
2525 assert!(args.enable_sccache);
2526 assert_eq!(args.sccache_dir, Some(PathBuf::from("/tmp/sccache")));
2527 assert_eq!(args.sccache_cache_size, Some("20G".to_string()));
2528 }
2529
2530 #[test]
2531 fn test_equals_syntax_config_with_equals_in_value() {
2532 let args = parse(&["cargo-cross", "build", "--config=build.jobs=4"]).unwrap();
2533 assert_eq!(args.cargo_config, vec!["build.jobs=4"]);
2534 }
2535
2536 #[test]
2537 fn test_equals_syntax_multiple_options() {
2538 let args = parse(&[
2539 "cargo-cross",
2540 "build",
2541 "-t=x86_64-unknown-linux-musl",
2542 "--profile=release",
2543 "-F=serde,json",
2544 "-j=8",
2545 "--crt-static=true",
2546 "--build-std=core,alloc",
2547 ])
2548 .unwrap();
2549 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2550 assert_eq!(args.profile, "release");
2551 assert_eq!(args.features, Some("serde,json".to_string()));
2552 assert_eq!(args.jobs, Some("8".to_string()));
2553 assert_eq!(args.crt_static, Some(true));
2554 assert_eq!(args.build_std, Some("core,alloc".to_string()));
2555 }
2556
2557 #[test]
2558 fn test_equals_syntax_toolchain() {
2559 let args = parse(&["cargo-cross", "build", "--toolchain=nightly-2024-01-01"]).unwrap();
2560 assert_eq!(args.toolchain, Some("nightly-2024-01-01".to_string()));
2561 }
2562
2563 #[test]
2564 fn test_equals_syntax_target_dir() {
2565 let args = parse(&["cargo-cross", "build", "--target-dir=/tmp/target"]).unwrap();
2566 assert_eq!(args.cargo_target_dir, Some(PathBuf::from("/tmp/target")));
2567 }
2568
2569 #[test]
2570 fn test_equals_syntax_z_flag() {
2571 let args = parse(&["cargo-cross", "build", "-Z=build-std"]).unwrap();
2572 assert_eq!(args.cargo_z_flags, vec!["build-std"]);
2573 }
2574
2575 #[test]
2576 fn test_equals_syntax_directory() {
2577 let args = parse(&["cargo-cross", "build", "-C=/path/to/project"]).unwrap();
2578 assert_eq!(args.cargo_cwd, Some(PathBuf::from("/path/to/project")));
2579 }
2580
2581 #[test]
2582 fn test_equals_syntax_message_format() {
2583 let args = parse(&["cargo-cross", "build", "--message-format=json"]).unwrap();
2584 assert_eq!(args.message_format, Some("json".to_string()));
2585 }
2586
2587 #[test]
2588 fn test_equals_syntax_color() {
2589 let args = parse(&["cargo-cross", "build", "--color=always"]).unwrap();
2590 assert_eq!(args.color, Some("always".to_string()));
2591 }
2592
2593 #[test]
2596 fn test_mixed_crt_static_then_flag() {
2597 let args = parse(&[
2598 "cargo-cross",
2599 "build",
2600 "--crt-static",
2601 "true",
2602 "--no-default-features",
2603 ])
2604 .unwrap();
2605 assert_eq!(args.crt_static, Some(true));
2606 assert!(args.no_default_features);
2607 }
2608
2609 #[test]
2610 fn test_mixed_crt_static_then_short_option() {
2611 let args = parse(&[
2612 "cargo-cross",
2613 "build",
2614 "--crt-static",
2615 "false",
2616 "-F",
2617 "serde",
2618 ])
2619 .unwrap();
2620 assert_eq!(args.crt_static, Some(false));
2621 assert_eq!(args.features, Some("serde".to_string()));
2622 }
2623
2624 #[test]
2625 fn test_mixed_crt_static_with_target() {
2626 let args = parse(&[
2627 "cargo-cross",
2628 "build",
2629 "--crt-static",
2630 "true",
2631 "-t",
2632 "x86_64-unknown-linux-musl",
2633 "--profile",
2634 "release",
2635 ])
2636 .unwrap();
2637 assert_eq!(args.crt_static, Some(true));
2638 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2639 assert_eq!(args.profile, "release");
2640 }
2641
2642 #[test]
2643 fn test_mixed_flag_then_crt_static() {
2644 let args = parse(&[
2645 "cargo-cross",
2646 "build",
2647 "--no-default-features",
2648 "--crt-static",
2649 "true",
2650 ])
2651 .unwrap();
2652 assert!(args.no_default_features);
2653 assert_eq!(args.crt_static, Some(true));
2654 }
2655
2656 #[test]
2657 fn test_mixed_multiple_flags_and_options() {
2658 let args = parse(&[
2659 "cargo-cross",
2660 "build",
2661 "-t",
2662 "aarch64-unknown-linux-musl",
2663 "--no-default-features",
2664 "-F",
2665 "serde,json",
2666 "--crt-static",
2667 "true",
2668 "--profile",
2669 "release",
2670 "-vv",
2671 ])
2672 .unwrap();
2673 assert_eq!(args.targets, vec!["aarch64-unknown-linux-musl"]);
2674 assert!(args.no_default_features);
2675 assert_eq!(args.features, Some("serde,json".to_string()));
2676 assert_eq!(args.crt_static, Some(true));
2677 assert_eq!(args.profile, "release");
2678 assert_eq!(args.verbose_level, 2);
2679 }
2680
2681 #[test]
2684 fn test_options_before_command_style() {
2685 let args = parse(&[
2687 "cargo-cross",
2688 "build",
2689 "--profile",
2690 "dev",
2691 "-t",
2692 "x86_64-unknown-linux-musl",
2693 "--features",
2694 "foo",
2695 ])
2696 .unwrap();
2697 assert_eq!(args.profile, "dev");
2698 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2699 assert_eq!(args.features, Some("foo".to_string()));
2700 }
2701
2702 #[test]
2703 fn test_interleaved_short_and_long_options() {
2704 let args = parse(&[
2705 "cargo-cross",
2706 "build",
2707 "-t",
2708 "x86_64-unknown-linux-musl",
2709 "--profile",
2710 "release",
2711 "-F",
2712 "foo",
2713 "--no-default-features",
2714 "-j",
2715 "4",
2716 "--locked",
2717 ])
2718 .unwrap();
2719 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2720 assert_eq!(args.profile, "release");
2721 assert_eq!(args.features, Some("foo".to_string()));
2722 assert!(args.no_default_features);
2723 assert_eq!(args.jobs, Some("4".to_string()));
2724 assert!(args.locked);
2725 }
2726
2727 #[test]
2730 fn test_verbose_single() {
2731 let args = parse(&["cargo-cross", "build", "-v"]).unwrap();
2732 assert_eq!(args.verbose_level, 1);
2733 }
2734
2735 #[test]
2736 fn test_verbose_double() {
2737 let args = parse(&["cargo-cross", "build", "-vv"]).unwrap();
2738 assert_eq!(args.verbose_level, 2);
2739 }
2740
2741 #[test]
2742 fn test_verbose_triple() {
2743 let args = parse(&["cargo-cross", "build", "-vvv"]).unwrap();
2744 assert_eq!(args.verbose_level, 3);
2745 }
2746
2747 #[test]
2748 fn test_verbose_separate() {
2749 let args = parse(&["cargo-cross", "build", "-v", "-v", "-v"]).unwrap();
2750 assert_eq!(args.verbose_level, 3);
2751 }
2752
2753 #[test]
2754 fn test_verbose_long_form() {
2755 let args = parse(&["cargo-cross", "build", "--verbose", "--verbose"]).unwrap();
2756 assert_eq!(args.verbose_level, 2);
2757 }
2758
2759 #[test]
2760 fn test_verbose_mixed_with_options() {
2761 let args = parse(&[
2762 "cargo-cross",
2763 "build",
2764 "-v",
2765 "-t",
2766 "x86_64-unknown-linux-musl",
2767 "-v",
2768 ])
2769 .unwrap();
2770 assert_eq!(args.verbose_level, 2);
2771 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2772 }
2773
2774 #[test]
2777 fn test_timings_without_value() {
2778 let args = parse(&["cargo-cross", "build", "--timings"]).unwrap();
2779 assert_eq!(args.timings, Some("true".to_string()));
2780 }
2781
2782 #[test]
2783 fn test_timings_with_value() {
2784 let args = parse(&["cargo-cross", "build", "--timings=html"]).unwrap();
2785 assert_eq!(args.timings, Some("html".to_string()));
2786 }
2787
2788 #[test]
2789 fn test_timings_followed_by_flag() {
2790 let args = parse(&["cargo-cross", "build", "--timings", "--locked"]).unwrap();
2791 assert_eq!(args.timings, Some("true".to_string()));
2792 assert!(args.locked);
2793 }
2794
2795 #[test]
2796 fn test_timings_followed_by_option() {
2797 let args = parse(&[
2798 "cargo-cross",
2799 "build",
2800 "--timings",
2801 "-t",
2802 "x86_64-unknown-linux-musl",
2803 ])
2804 .unwrap();
2805 assert_eq!(args.timings, Some("true".to_string()));
2806 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2807 }
2808
2809 #[test]
2812 fn test_multiple_targets_comma_separated() {
2813 let args = parse(&[
2814 "cargo-cross",
2815 "build",
2816 "-t",
2817 "x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,armv7-unknown-linux-musleabihf",
2818 ])
2819 .unwrap();
2820 assert_eq!(args.targets.len(), 3);
2821 assert_eq!(args.targets[0], "x86_64-unknown-linux-musl");
2822 assert_eq!(args.targets[1], "aarch64-unknown-linux-musl");
2823 assert_eq!(args.targets[2], "armv7-unknown-linux-musleabihf");
2824 }
2825
2826 #[test]
2827 fn test_multiple_targets_repeated_option() {
2828 let args = parse(&[
2829 "cargo-cross",
2830 "build",
2831 "-t",
2832 "x86_64-unknown-linux-musl",
2833 "-t",
2834 "aarch64-unknown-linux-musl",
2835 ])
2836 .unwrap();
2837 assert_eq!(args.targets.len(), 2);
2838 }
2839
2840 #[test]
2841 fn test_multiple_rustflags() {
2842 let args = parse(&[
2843 "cargo-cross",
2844 "build",
2845 "--rustflag",
2846 "-C opt-level=3",
2847 "--rustflag",
2848 "-C lto=thin",
2849 ])
2850 .unwrap();
2851 assert_eq!(args.rustflags.len(), 2);
2852 assert_eq!(args.rustflags[0], "-C opt-level=3");
2853 assert_eq!(args.rustflags[1], "-C lto=thin");
2854 }
2855
2856 #[test]
2857 fn test_multiple_config_flags() {
2858 let args = parse(&[
2859 "cargo-cross",
2860 "build",
2861 "--config",
2862 "build.jobs=4",
2863 "--config",
2864 "profile.release.lto=true",
2865 ])
2866 .unwrap();
2867 assert_eq!(args.cargo_config.len(), 2);
2868 }
2869
2870 #[test]
2871 fn test_multiple_z_flags() {
2872 let args = parse(&[
2873 "cargo-cross",
2874 "build",
2875 "-Z",
2876 "build-std",
2877 "-Z",
2878 "unstable-options",
2879 ])
2880 .unwrap();
2881 assert_eq!(args.cargo_z_flags.len(), 2);
2882 }
2883
2884 #[test]
2887 fn test_cflags_with_hyphen() {
2888 let args = parse(&["cargo-cross", "build", "--cflags", "-O2 -Wall"]).unwrap();
2889 assert_eq!(args.cflags, Some("-O2 -Wall".to_string()));
2890 }
2891
2892 #[test]
2893 fn test_ldflags_with_hyphen() {
2894 let args = parse(&["cargo-cross", "build", "--ldflags", "-L/usr/local/lib"]).unwrap();
2895 assert_eq!(args.ldflags, Some("-L/usr/local/lib".to_string()));
2896 }
2897
2898 #[test]
2899 fn test_rustflag_with_hyphen() {
2900 let args = parse(&["cargo-cross", "build", "--rustflag", "-C target-cpu=native"]).unwrap();
2901 assert_eq!(args.rustflags, vec!["-C target-cpu=native"]);
2902 }
2903
2904 #[test]
2907 fn test_passthrough_single() {
2908 let args = parse(&["cargo-cross", "build", "--", "--nocapture"]).unwrap();
2909 assert_eq!(args.passthrough_args, vec!["--nocapture"]);
2910 }
2911
2912 #[test]
2913 fn test_passthrough_multiple() {
2914 let args = parse(&[
2915 "cargo-cross",
2916 "test",
2917 "--",
2918 "--nocapture",
2919 "--test-threads=1",
2920 ])
2921 .unwrap();
2922 assert_eq!(
2923 args.passthrough_args,
2924 vec!["--nocapture", "--test-threads=1"]
2925 );
2926 }
2927
2928 #[test]
2929 fn test_passthrough_with_hyphen_values() {
2930 let args = parse(&["cargo-cross", "build", "--", "-v", "--foo", "-bar"]).unwrap();
2931 assert_eq!(args.passthrough_args, vec!["-v", "--foo", "-bar"]);
2932 }
2933
2934 #[test]
2935 fn test_passthrough_after_options() {
2936 let args = parse(&[
2937 "cargo-cross",
2938 "build",
2939 "-t",
2940 "x86_64-unknown-linux-musl",
2941 "--profile",
2942 "release",
2943 "--",
2944 "--foo",
2945 "--bar",
2946 ])
2947 .unwrap();
2948 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2949 assert_eq!(args.profile, "release");
2950 assert_eq!(args.passthrough_args, vec!["--foo", "--bar"]);
2951 }
2952
2953 #[test]
2956 fn test_alias_targets() {
2957 let args = parse(&[
2958 "cargo-cross",
2959 "build",
2960 "--targets",
2961 "x86_64-unknown-linux-musl",
2962 ])
2963 .unwrap();
2964 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2965 }
2966
2967 #[test]
2968 fn test_alias_workspace_all() {
2969 let args = parse(&["cargo-cross", "build", "--all"]).unwrap();
2970 assert!(args.workspace);
2971 }
2972
2973 #[test]
2974 fn test_alias_rustflags() {
2975 let args = parse(&["cargo-cross", "build", "--rustflags", "-C lto"]).unwrap();
2976 assert_eq!(args.rustflags, vec!["-C lto"]);
2977 }
2978
2979 #[test]
2980 fn test_alias_trim_paths() {
2981 let args = parse(&["cargo-cross", "build", "--trim-paths", "all"]).unwrap();
2982 assert_eq!(args.cargo_trim_paths, Some("all".to_string()));
2983 }
2984
2985 #[test]
2986 fn test_trim_paths_no_value() {
2987 let args = parse(&["cargo-cross", "build", "--trim-paths"]).unwrap();
2989 assert_eq!(args.cargo_trim_paths, Some("true".to_string()));
2990 }
2991
2992 #[test]
2993 fn test_cargo_trim_paths_no_value() {
2994 let args = parse(&["cargo-cross", "build", "--cargo-trim-paths"]).unwrap();
2996 assert_eq!(args.cargo_trim_paths, Some("true".to_string()));
2997 }
2998
2999 #[test]
3000 fn test_rustc_bootstrap_no_value() {
3001 let args = parse(&["cargo-cross", "build", "--rustc-bootstrap"]).unwrap();
3003 assert_eq!(args.rustc_bootstrap, Some("1".to_string()));
3004 }
3005
3006 #[test]
3007 fn test_rustc_bootstrap_with_value() {
3008 let args = parse(&["cargo-cross", "build", "--rustc-bootstrap", "mycrate"]).unwrap();
3009 assert_eq!(args.rustc_bootstrap, Some("mycrate".to_string()));
3010 }
3011
3012 #[test]
3015 fn test_command_alias_b() {
3016 let args = parse(&["cargo-cross", "b"]).unwrap();
3017 assert_eq!(args.command, Command::build());
3018 }
3019
3020 #[test]
3021 fn test_command_alias_c() {
3022 let args = parse(&["cargo-cross", "c"]).unwrap();
3023 assert_eq!(args.command, Command::check());
3024 }
3025
3026 #[test]
3027 fn test_command_alias_r() {
3028 let args = parse(&["cargo-cross", "r"]).unwrap();
3029 assert_eq!(args.command, Command::run());
3030 }
3031
3032 #[test]
3033 fn test_command_alias_t() {
3034 let args = parse(&["cargo-cross", "t"]).unwrap();
3035 assert_eq!(args.command, Command::test());
3036 }
3037
3038 #[test]
3041 fn test_requires_exclude_needs_workspace() {
3042 let result = parse(&["cargo-cross", "build", "--exclude", "foo"]);
3043 assert!(
3044 result.is_err() || {
3045 false
3047 }
3048 );
3049 }
3050
3051 #[test]
3052 fn test_requires_exclude_with_workspace() {
3053 let args = parse(&["cargo-cross", "build", "--workspace", "--exclude", "foo"]).unwrap();
3054 assert!(args.workspace);
3055 assert_eq!(args.exclude, Some("foo".to_string()));
3056 }
3057
3058 #[test]
3059 fn test_requires_build_std_features_with_build_std() {
3060 let args = parse(&[
3061 "cargo-cross",
3062 "build",
3063 "--build-std",
3064 "core,alloc",
3065 "--build-std-features",
3066 "panic_immediate_abort",
3067 ])
3068 .unwrap();
3069 assert_eq!(args.build_std, Some("core,alloc".to_string()));
3070 assert_eq!(
3071 args.build_std_features,
3072 Some("panic_immediate_abort".to_string())
3073 );
3074 }
3075
3076 #[test]
3079 fn test_conflicts_quiet_verbose() {
3080 let result = parse(&["cargo-cross", "build", "--quiet", "--verbose"]);
3081 assert!(
3083 result.is_err() || {
3084 false
3086 }
3087 );
3088 }
3089
3090 #[test]
3091 fn test_conflicts_features_all_features() {
3092 let result = parse(&[
3093 "cargo-cross",
3094 "build",
3095 "--features",
3096 "foo",
3097 "--all-features",
3098 ]);
3099 assert!(result.is_err());
3100 }
3101
3102 #[test]
3103 fn test_no_toolchain_setup() {
3104 let args = parse(&["cargo-cross", "build", "--no-toolchain-setup"]).unwrap();
3105 assert!(args.no_toolchain_setup);
3106 }
3107
3108 #[test]
3109 fn test_linker_with_no_toolchain_setup() {
3110 let args = parse(&[
3112 "cargo-cross",
3113 "build",
3114 "--linker",
3115 "/usr/bin/ld",
3116 "--no-toolchain-setup",
3117 ])
3118 .unwrap();
3119 assert!(args.no_toolchain_setup);
3120 assert_eq!(args.linker, Some(PathBuf::from("/usr/bin/ld")));
3121 }
3122
3123 #[test]
3126 fn test_real_world_linux_musl_build() {
3127 let args = parse(&[
3128 "cargo-cross",
3129 "+nightly",
3130 "build",
3131 "-t",
3132 "x86_64-unknown-linux-musl",
3133 "--profile",
3134 "release",
3135 "--crt-static",
3136 "true",
3137 "--no-default-features",
3138 "-F",
3139 "serde,json",
3140 "-j",
3141 "8",
3142 "--locked",
3143 ])
3144 .unwrap();
3145 assert_eq!(args.toolchain, Some("nightly".to_string()));
3146 assert_eq!(args.command, Command::build());
3147 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3148 assert_eq!(args.profile, "release");
3149 assert_eq!(args.crt_static, Some(true));
3150 assert!(args.no_default_features);
3151 assert_eq!(args.features, Some("serde,json".to_string()));
3152 assert_eq!(args.jobs, Some("8".to_string()));
3153 assert!(args.locked);
3154 }
3155
3156 #[test]
3157 fn test_real_world_multi_target_build() {
3158 let args = parse(&[
3159 "cargo-cross",
3160 "build",
3161 "-t",
3162 "x86_64-unknown-linux-musl,aarch64-unknown-linux-musl",
3163 "--profile",
3164 "release",
3165 "--build-std",
3166 "core,alloc",
3167 "--build-std-features",
3168 "panic_immediate_abort",
3169 "-vv",
3170 ])
3171 .unwrap();
3172 assert_eq!(args.targets.len(), 2);
3173 assert_eq!(args.build_std, Some("core,alloc".to_string()));
3174 assert_eq!(
3175 args.build_std_features,
3176 Some("panic_immediate_abort".to_string())
3177 );
3178 assert_eq!(args.verbose_level, 2);
3179 }
3180
3181 #[test]
3182 fn test_real_world_test_with_passthrough() {
3183 let args = parse(&[
3184 "cargo-cross",
3185 "test",
3186 "-t",
3187 "x86_64-unknown-linux-musl",
3188 "--",
3189 "--nocapture",
3190 "--test-threads=1",
3191 ])
3192 .unwrap();
3193 assert_eq!(args.command, Command::test());
3194 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3195 assert_eq!(
3196 args.passthrough_args,
3197 vec!["--nocapture", "--test-threads=1"]
3198 );
3199 }
3200
3201 #[test]
3202 fn test_real_world_with_compiler_options() {
3203 let args = parse(&[
3204 "cargo-cross",
3205 "build",
3206 "-t",
3207 "aarch64-unknown-linux-musl",
3208 "--cc",
3209 "/opt/cross/bin/aarch64-linux-musl-gcc",
3210 "--cxx",
3211 "/opt/cross/bin/aarch64-linux-musl-g++",
3212 "--ar",
3213 "/opt/cross/bin/aarch64-linux-musl-ar",
3214 "--cflags",
3215 "-O2 -march=armv8-a",
3216 ])
3217 .unwrap();
3218 assert_eq!(args.targets, vec!["aarch64-unknown-linux-musl"]);
3219 assert!(args.cc.is_some());
3220 assert!(args.cxx.is_some());
3221 assert!(args.ar.is_some());
3222 assert_eq!(args.cflags, Some("-O2 -march=armv8-a".to_string()));
3223 }
3224
3225 #[test]
3226 fn test_real_world_sccache_build() {
3227 let args = parse(&[
3228 "cargo-cross",
3229 "build",
3230 "-t",
3231 "x86_64-unknown-linux-musl",
3232 "--enable-sccache",
3233 "--sccache-dir",
3234 "/tmp/sccache",
3235 "--sccache-cache-size",
3236 "10G",
3237 ])
3238 .unwrap();
3239 assert!(args.enable_sccache);
3240 assert_eq!(args.sccache_dir, Some(PathBuf::from("/tmp/sccache")));
3241 assert_eq!(args.sccache_cache_size, Some("10G".to_string()));
3242 }
3243
3244 #[test]
3245 fn test_real_world_workspace_build() {
3246 let args = parse(&[
3247 "cargo-cross",
3248 "build",
3249 "--workspace",
3250 "--exclude",
3251 "test-crate",
3252 "-t",
3253 "x86_64-unknown-linux-musl",
3254 "--profile",
3255 "release",
3256 ])
3257 .unwrap();
3258 assert!(args.workspace);
3259 assert_eq!(args.exclude, Some("test-crate".to_string()));
3260 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3261 }
3262
3263 #[test]
3266 fn test_edge_case_equals_in_value() {
3267 let args = parse(&[
3268 "cargo-cross",
3269 "build",
3270 "--config",
3271 "build.rustflags=['-C', 'opt-level=3']",
3272 ])
3273 .unwrap();
3274 assert_eq!(
3275 args.cargo_config,
3276 vec!["build.rustflags=['-C', 'opt-level=3']"]
3277 );
3278 }
3279
3280 #[test]
3281 fn test_edge_case_empty_passthrough() {
3282 let args = parse(&["cargo-cross", "build", "--"]).unwrap();
3283 assert!(args.passthrough_args.is_empty());
3284 }
3285
3286 #[test]
3287 fn test_edge_case_target_with_numbers() {
3288 let args = parse(&[
3289 "cargo-cross",
3290 "build",
3291 "-t",
3292 "armv7-unknown-linux-musleabihf",
3293 ])
3294 .unwrap();
3295 assert_eq!(args.targets, vec!["armv7-unknown-linux-musleabihf"]);
3296 }
3297
3298 #[test]
3299 fn test_edge_case_all_bool_options() {
3300 let args = parse(&[
3301 "cargo-cross",
3302 "build",
3303 "--no-default-features",
3304 "--workspace",
3305 "--bins",
3306 "--lib",
3307 "--examples",
3308 "--tests",
3309 "--benches",
3310 "--all-targets",
3311 "--locked",
3312 "--offline",
3313 "--keep-going",
3314 ])
3315 .unwrap();
3316 assert!(args.no_default_features);
3317 assert!(args.workspace);
3318 assert!(args.build_bins);
3319 assert!(args.build_lib);
3320 assert!(args.build_examples);
3321 assert!(args.build_tests);
3322 assert!(args.build_benches);
3323 assert!(args.build_all_targets);
3324 assert!(args.locked);
3325 assert!(args.offline);
3326 assert!(args.keep_going);
3327 }
3328
3329 #[test]
3330 fn test_edge_case_mixed_equals_and_space() {
3331 let args = parse(&[
3332 "cargo-cross",
3333 "build",
3334 "-t=x86_64-unknown-linux-musl",
3335 "--profile",
3336 "release",
3337 "-F=serde",
3338 "--crt-static",
3339 "true",
3340 ])
3341 .unwrap();
3342 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3343 assert_eq!(args.profile, "release");
3344 assert_eq!(args.features, Some("serde".to_string()));
3345 assert_eq!(args.crt_static, Some(true));
3346 }
3347
3348 #[test]
3349 fn test_edge_case_directory_option() {
3350 let args = parse(&[
3351 "cargo-cross",
3352 "build",
3353 "-C",
3354 "/path/to/project",
3355 "-t",
3356 "x86_64-unknown-linux-musl",
3357 ])
3358 .unwrap();
3359 assert_eq!(args.cargo_cwd, Some(PathBuf::from("/path/to/project")));
3360 }
3361
3362 #[test]
3363 fn test_edge_case_manifest_path() {
3364 let args = parse(&[
3365 "cargo-cross",
3366 "build",
3367 "--manifest-path",
3368 "/path/to/Cargo.toml",
3369 ])
3370 .unwrap();
3371 assert_eq!(
3372 args.manifest_path,
3373 Some(PathBuf::from("/path/to/Cargo.toml"))
3374 );
3375 }
3376
3377 #[test]
3380 fn test_cargo_cross_style_build() {
3381 let args: Vec<String> = vec![
3382 "cargo-cross".to_string(),
3383 "cross".to_string(),
3384 "build".to_string(),
3385 "-t".to_string(),
3386 "x86_64-unknown-linux-musl".to_string(),
3387 ];
3388 match parse_args_from(args).unwrap() {
3389 ParseResult::Build(args) => {
3390 assert_eq!(args.command, Command::build());
3391 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3392 }
3393 _ => panic!("expected Build"),
3394 }
3395 }
3396
3397 #[test]
3398 fn test_cargo_cross_style_with_toolchain() {
3399 let args: Vec<String> = vec![
3400 "cargo-cross".to_string(),
3401 "cross".to_string(),
3402 "+nightly".to_string(),
3403 "build".to_string(),
3404 ];
3405 match parse_args_from(args).unwrap() {
3406 ParseResult::Build(args) => {
3407 assert_eq!(args.toolchain, Some("nightly".to_string()));
3408 assert_eq!(args.command, Command::build());
3409 }
3410 _ => panic!("expected Build"),
3411 }
3412 }
3413
3414 #[test]
3415 fn test_cargo_cross_style_targets() {
3416 let args: Vec<String> = vec![
3417 "cargo-cross".to_string(),
3418 "cross".to_string(),
3419 "targets".to_string(),
3420 ];
3421 match parse_args_from(args).unwrap() {
3422 ParseResult::ShowTargets(_) => {}
3423 _ => panic!("expected ShowTargets"),
3424 }
3425 }
3426
3427 #[test]
3430 fn test_github_proxy_mirror_alias() {
3431 let args = parse(&[
3432 "cargo-cross",
3433 "build",
3434 "--github-proxy-mirror",
3435 "https://mirror.example.com/",
3436 ])
3437 .unwrap();
3438 assert_eq!(
3439 args.github_proxy,
3440 Some("https://mirror.example.com/".to_string())
3441 );
3442 }
3443
3444 #[test]
3445 fn test_github_proxy_original() {
3446 let args = parse(&[
3447 "cargo-cross",
3448 "build",
3449 "--github-proxy",
3450 "https://proxy.example.com/",
3451 ])
3452 .unwrap();
3453 assert_eq!(
3454 args.github_proxy,
3455 Some("https://proxy.example.com/".to_string())
3456 );
3457 }
3458
3459 #[test]
3460 fn test_release_flag_short() {
3461 let args = parse(&["cargo-cross", "build", "-r"]).unwrap();
3462 assert!(args.release);
3463 assert_eq!(args.profile, "release");
3464 }
3465
3466 #[test]
3467 fn test_release_flag_long() {
3468 let args = parse(&["cargo-cross", "build", "--release"]).unwrap();
3469 assert!(args.release);
3470 assert_eq!(args.profile, "release");
3471 }
3472
3473 #[test]
3474 fn test_toolchain_option() {
3475 let args = parse(&["cargo-cross", "build", "--toolchain", "nightly"]).unwrap();
3476 assert_eq!(args.toolchain, Some("nightly".to_string()));
3477 }
3478
3479 #[test]
3480 fn test_toolchain_option_with_version() {
3481 let args = parse(&["cargo-cross", "build", "--toolchain", "1.75.0"]).unwrap();
3482 assert_eq!(args.toolchain, Some("1.75.0".to_string()));
3483 }
3484
3485 #[test]
3486 fn test_toolchain_plus_syntax_takes_precedence() {
3487 let args = parse(&["cargo-cross", "+nightly", "build", "--toolchain", "stable"]).unwrap();
3488 assert_eq!(args.toolchain, Some("nightly".to_string()));
3490 }
3491
3492 #[test]
3493 fn test_target_dir_alias() {
3494 let args = parse(&["cargo-cross", "build", "--target-dir", "/tmp/target"]).unwrap();
3495 assert_eq!(args.cargo_target_dir, Some(PathBuf::from("/tmp/target")));
3496 }
3497
3498 #[test]
3499 fn test_cargo_target_dir_original() {
3500 let args = parse(&["cargo-cross", "build", "--cargo-target-dir", "/tmp/target"]).unwrap();
3501 assert_eq!(args.cargo_target_dir, Some(PathBuf::from("/tmp/target")));
3502 }
3503
3504 #[test]
3505 fn test_args_alias() {
3506 let args = parse(&["cargo-cross", "build", "--args", "--verbose"]).unwrap();
3507 assert_eq!(args.cargo_args, vec!["--verbose"]);
3508 }
3509
3510 #[test]
3511 fn test_cargo_args_original() {
3512 let args = parse(&["cargo-cross", "build", "--cargo-args", "--verbose"]).unwrap();
3513 assert_eq!(args.cargo_args, vec!["--verbose"]);
3514 }
3515
3516 #[test]
3517 fn test_cargo_args_multiple() {
3518 let args = parse(&[
3519 "cargo-cross",
3520 "build",
3521 "--cargo-args",
3522 "--verbose",
3523 "--cargo-args",
3524 "--locked",
3525 ])
3526 .unwrap();
3527 assert_eq!(args.cargo_args, vec!["--verbose", "--locked"]);
3528 }
3529
3530 #[test]
3533 fn test_invalid_target_triple_uppercase() {
3534 let result = parse(&["cargo-cross", "build", "-t", "X86_64-unknown-linux-musl"]);
3535 assert!(result.is_err());
3536 let err = result.unwrap_err();
3537 assert!(
3538 matches!(err, CrossError::InvalidTargetTriple { target, char } if target == "X86_64-unknown-linux-musl" && char == 'X')
3539 );
3540 }
3541
3542 #[test]
3543 fn test_glob_pattern_matches_target() {
3544 let args = parse(&["cargo-cross", "build", "-t", "x86_64*unknown-linux-musl"]).unwrap();
3546 assert!(args
3547 .targets
3548 .contains(&"x86_64-unknown-linux-musl".to_string()));
3549 }
3550
3551 #[test]
3552 fn test_invalid_target_triple_slash() {
3553 let result = parse(&["cargo-cross", "build", "-t", "x86_64/unknown-linux-musl"]);
3555 assert!(result.is_err());
3556 let err = result.unwrap_err();
3557 assert!(
3558 matches!(err, CrossError::InvalidTargetTriple { target, char } if target == "x86_64/unknown-linux-musl" && char == '/')
3559 );
3560 }
3561
3562 #[test]
3563 fn test_invalid_target_triple_dot() {
3564 let result = parse(&["cargo-cross", "build", "-t", "x86_64.unknown-linux-musl"]);
3565 assert!(result.is_err());
3566 let err = result.unwrap_err();
3567 assert!(
3568 matches!(err, CrossError::InvalidTargetTriple { target, char } if target == "x86_64.unknown-linux-musl" && char == '.')
3569 );
3570 }
3571
3572 #[test]
3573 fn test_no_matching_targets_glob() {
3574 let result = parse(&["cargo-cross", "build", "-t", "*mingw*"]);
3575 assert!(result.is_err());
3576 let err = result.unwrap_err();
3577 assert!(matches!(
3578 err,
3579 CrossError::NoMatchingTargets { pattern } if pattern == "*mingw*"
3580 ));
3581 }
3582
3583 #[test]
3584 fn test_no_matching_targets_glob_complex() {
3585 let result = parse(&["cargo-cross", "build", "-t", "*nonexistent-platform*"]);
3586 assert!(result.is_err());
3587 let err = result.unwrap_err();
3588 assert!(matches!(
3589 err,
3590 CrossError::NoMatchingTargets { pattern } if pattern == "*nonexistent-platform*"
3591 ));
3592 }
3593
3594 #[test]
3595 fn test_valid_target_triple_with_numbers() {
3596 let args = parse(&[
3597 "cargo-cross",
3598 "build",
3599 "-t",
3600 "armv7-unknown-linux-gnueabihf",
3601 ])
3602 .unwrap();
3603 assert_eq!(args.targets, vec!["armv7-unknown-linux-gnueabihf"]);
3604 }
3605
3606 #[test]
3607 fn test_valid_target_triple_underscore() {
3608 let args = parse(&["cargo-cross", "build", "-t", "x86_64_unknown_linux_musl"]).unwrap();
3609 assert_eq!(args.targets, vec!["x86_64_unknown_linux_musl"]);
3610 }
3611
3612 #[test]
3613 fn test_valid_glob_pattern_matches() {
3614 let args = parse(&["cargo-cross", "build", "-t", "*-linux-musl"]).unwrap();
3615 assert!(!args.targets.is_empty());
3616 for target in &args.targets {
3617 assert!(target.ends_with("-linux-musl"));
3618 }
3619 }
3620
3621 #[test]
3622 fn test_is_glob_pattern() {
3623 assert!(is_glob_pattern("*-linux-musl"));
3624 assert!(is_glob_pattern("x86_64-*-linux"));
3625 assert!(is_glob_pattern("x86_64-?-linux"));
3626 assert!(is_glob_pattern("[ab]-linux"));
3627 assert!(!is_glob_pattern("x86_64-unknown-linux-musl"));
3628 assert!(!is_glob_pattern("aarch64-linux-android"));
3629 }
3630
3631 #[test]
3632 fn test_validate_target_triple() {
3633 assert!(validate_target_triple("x86_64-unknown-linux-musl").is_ok());
3634 assert!(validate_target_triple("aarch64-unknown-linux-gnu").is_ok());
3635 assert!(validate_target_triple("armv7-unknown-linux-gnueabihf").is_ok());
3636 assert!(validate_target_triple("i686-pc-windows-msvc").is_ok());
3637 assert!(validate_target_triple("x86_64-pc-windows-msvc").is_ok());
3638 assert!(validate_target_triple("X86_64-unknown-linux-musl").is_err()); assert!(validate_target_triple("x86_64*linux").is_err()); assert!(validate_target_triple("x86_64.linux").is_err()); assert!(validate_target_triple("x86_64 linux").is_err()); }
3643
3644 #[test]
3647 fn test_short_concat_features() {
3648 let args = parse(&["cargo-cross", "build", "-Ffoo,bar"]).unwrap();
3649 assert_eq!(args.features, Some("foo,bar".to_string()));
3650 }
3651
3652 #[test]
3653 fn test_short_concat_jobs() {
3654 let args = parse(&["cargo-cross", "build", "-j4"]).unwrap();
3655 assert_eq!(args.jobs, Some("4".to_string()));
3656 }
3657
3658 #[test]
3659 fn test_short_concat_package() {
3660 let args = parse(&["cargo-cross", "build", "-pmypackage"]).unwrap();
3661 assert_eq!(args.package, Some("mypackage".to_string()));
3662 }
3663
3664 #[test]
3665 fn test_short_concat_target() {
3666 let args = parse(&["cargo-cross", "build", "-tx86_64-unknown-linux-musl"]).unwrap();
3667 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3668 }
3669
3670 #[test]
3671 fn test_short_concat_z_flag() {
3672 let args = parse(&["cargo-cross", "build", "-Zbuild-std"]).unwrap();
3673 assert_eq!(args.cargo_z_flags, vec!["build-std"]);
3674 }
3675
3676 #[test]
3677 fn test_short_concat_directory() {
3678 let args = parse(&["cargo-cross", "build", "-C/path/to/project"]).unwrap();
3679 assert_eq!(args.cargo_cwd, Some(PathBuf::from("/path/to/project")));
3680 }
3681
3682 #[test]
3683 fn test_short_concat_multiple() {
3684 let args = parse(&[
3685 "cargo-cross",
3686 "build",
3687 "-tx86_64-unknown-linux-musl",
3688 "-Ffoo,bar",
3689 "-j8",
3690 "-pmypkg",
3691 ])
3692 .unwrap();
3693 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3694 assert_eq!(args.features, Some("foo,bar".to_string()));
3695 assert_eq!(args.jobs, Some("8".to_string()));
3696 assert_eq!(args.package, Some("mypkg".to_string()));
3697 }
3698
3699 #[test]
3700 fn test_short_concat_mixed_with_space() {
3701 let args = parse(&[
3702 "cargo-cross",
3703 "build",
3704 "-j4",
3705 "-t",
3706 "x86_64-unknown-linux-musl",
3707 "-Fbar",
3708 ])
3709 .unwrap();
3710 assert_eq!(args.jobs, Some("4".to_string()));
3711 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3712 assert_eq!(args.features, Some("bar".to_string()));
3713 }
3714
3715 #[test]
3718 fn test_parse_env_args_not_set() {
3719 std::env::remove_var("TEST_PARSE_ENV_ARGS_NOT_SET");
3720 let result = parse_env_args("TEST_PARSE_ENV_ARGS_NOT_SET");
3721 assert!(result.is_none());
3722 }
3723
3724 #[test]
3725 fn test_parse_env_args_empty() {
3726 std::env::set_var("TEST_PARSE_ENV_ARGS_EMPTY", "");
3727 let result = parse_env_args("TEST_PARSE_ENV_ARGS_EMPTY");
3728 assert!(result.is_none());
3729 std::env::remove_var("TEST_PARSE_ENV_ARGS_EMPTY");
3730 }
3731
3732 #[test]
3733 fn test_parse_env_args_simple() {
3734 std::env::set_var("TEST_PARSE_ENV_ARGS_SIMPLE", "--verbose --locked");
3735 let result = parse_env_args("TEST_PARSE_ENV_ARGS_SIMPLE");
3736 assert_eq!(
3737 result,
3738 Some(vec!["--verbose".to_string(), "--locked".to_string()])
3739 );
3740 std::env::remove_var("TEST_PARSE_ENV_ARGS_SIMPLE");
3741 }
3742
3743 #[test]
3744 fn test_parse_env_args_with_single_quotes() {
3745 std::env::set_var(
3746 "TEST_PARSE_ENV_ARGS_SINGLE_QUOTES",
3747 "--config 'build.jobs=4' --verbose",
3748 );
3749 let result = parse_env_args("TEST_PARSE_ENV_ARGS_SINGLE_QUOTES");
3750 assert_eq!(
3751 result,
3752 Some(vec![
3753 "--config".to_string(),
3754 "build.jobs=4".to_string(),
3755 "--verbose".to_string()
3756 ])
3757 );
3758 std::env::remove_var("TEST_PARSE_ENV_ARGS_SINGLE_QUOTES");
3759 }
3760
3761 #[test]
3762 fn test_parse_env_args_with_double_quotes() {
3763 std::env::set_var(
3764 "TEST_PARSE_ENV_ARGS_DOUBLE_QUOTES",
3765 "--message-format \"json with spaces\"",
3766 );
3767 let result = parse_env_args("TEST_PARSE_ENV_ARGS_DOUBLE_QUOTES");
3768 assert_eq!(
3769 result,
3770 Some(vec![
3771 "--message-format".to_string(),
3772 "json with spaces".to_string()
3773 ])
3774 );
3775 std::env::remove_var("TEST_PARSE_ENV_ARGS_DOUBLE_QUOTES");
3776 }
3777
3778 #[test]
3779 fn test_parse_env_args_complex() {
3780 std::env::set_var(
3781 "TEST_PARSE_ENV_ARGS_COMPLEX",
3782 "--config 'key=\"value with spaces\"' --verbose",
3783 );
3784 let result = parse_env_args("TEST_PARSE_ENV_ARGS_COMPLEX");
3785 assert_eq!(
3786 result,
3787 Some(vec![
3788 "--config".to_string(),
3789 "key=\"value with spaces\"".to_string(),
3790 "--verbose".to_string()
3791 ])
3792 );
3793 std::env::remove_var("TEST_PARSE_ENV_ARGS_COMPLEX");
3794 }
3795
3796 #[test]
3797 fn test_parse_env_args_whitespace_only() {
3798 std::env::set_var("TEST_PARSE_ENV_ARGS_WHITESPACE", " ");
3799 let result = parse_env_args("TEST_PARSE_ENV_ARGS_WHITESPACE");
3800 assert!(result.is_none());
3801 std::env::remove_var("TEST_PARSE_ENV_ARGS_WHITESPACE");
3802 }
3803
3804 #[test]
3805 fn test_musl_target_with_default_glibc() {
3806 let args = parse(&[
3808 "cargo-cross",
3809 "build",
3810 "-t",
3811 "aarch64_be-unknown-linux-musl",
3812 ])
3813 .unwrap();
3814 assert_eq!(args.targets, vec!["aarch64_be-unknown-linux-musl"]);
3815 assert_eq!(args.glibc_version, ""); }
3817}