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::{Args as ClapArgs, CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint};
14use std::path::PathBuf;
15use std::sync::LazyLock;
16
17const BIN_NAME: &str = env!("CARGO_PKG_NAME");
19
20macro_rules! define_subcommand {
22 ($name:literal) => {
23 const SUBCOMMAND: &str = $name;
24 const CARGO_DISPLAY_NAME: &str = concat!("cargo ", $name);
25 };
26}
27define_subcommand!("cross");
28
29static PROGRAM_NAME: LazyLock<&'static str> = LazyLock::new(|| {
31 let args: Vec<String> = std::env::args().collect();
32 let is_cargo_subcommand = std::env::var("CARGO").is_ok()
33 && std::env::var("CARGO_HOME").is_ok()
34 && args.get(1).map(String::as_str) == Some(SUBCOMMAND);
35
36 if is_cargo_subcommand {
37 CARGO_DISPLAY_NAME
38 } else {
39 BIN_NAME
40 }
41});
42
43#[must_use]
45pub fn program_name() -> &'static str {
46 *PROGRAM_NAME
47}
48
49fn cli_styles() -> Styles {
51 Styles::styled()
52 .header(AnsiColor::BrightCyan.on_default() | Effects::BOLD)
53 .usage(AnsiColor::BrightCyan.on_default() | Effects::BOLD)
54 .literal(AnsiColor::BrightGreen.on_default())
55 .placeholder(AnsiColor::BrightMagenta.on_default())
56 .valid(AnsiColor::BrightGreen.on_default())
57 .invalid(AnsiColor::BrightRed.on_default())
58 .error(AnsiColor::BrightRed.on_default() | Effects::BOLD)
59}
60
61#[derive(Parser, Debug)]
63#[command(name = "cargo-cross", version)]
64#[command(about = "Cross-compilation tool for Rust projects, no Docker required")]
65#[command(long_about = "\
66Cross-compilation tool for Rust projects.
67
68This tool provides cross-compilation support for Rust projects across multiple
69platforms including Linux (musl/gnu), Windows, macOS, FreeBSD, iOS, and Android.
70It automatically downloads and configures the appropriate cross-compiler toolchains.")]
71#[command(propagate_version = true)]
72#[command(arg_required_else_help = true)]
73#[command(styles = cli_styles())]
74#[command(override_usage = "cargo-cross [+toolchain] <COMMAND> [OPTIONS]")]
75#[command(after_help = "\
76Use 'cargo-cross <COMMAND> --help' for more information about a command.
77
78TOOLCHAIN:
79 If the first argument begins with +, it will be interpreted as a Rust toolchain
80 name (such as +nightly, +stable, or +1.75.0). This follows the same convention
81 as rustup and cargo.
82
83EXAMPLES:
84 cargo-cross build -t x86_64-unknown-linux-musl
85 cargo-cross +nightly build -t aarch64-unknown-linux-gnu --profile release
86 cargo-cross build -t '*-linux-musl' --crt-static true
87 cargo-cross test -t x86_64-unknown-linux-musl -- --nocapture")]
88pub struct Cli {
89 #[command(subcommand)]
90 pub command: CliCommand,
91}
92
93#[derive(Subcommand, Debug)]
94pub enum CliCommand {
95 #[command(visible_alias = "b")]
97 #[command(long_about = "\
98Compile the current package and all of its dependencies.
99
100When no target selection options are given, this tool will build all binary
101and library targets of the selected packages.")]
102 Build(BuildArgs),
103
104 #[command(visible_alias = "c")]
106 #[command(long_about = "\
107Check the current package and all of its dependencies for errors.
108
109This will essentially compile packages without performing the final step of
110code generation, which is faster than running build.")]
111 Check(BuildArgs),
112
113 #[command(visible_alias = "r")]
115 #[command(long_about = "\
116Run a binary or example of the local package.
117
118For cross-compilation targets, QEMU user-mode emulation is used to run the binary.")]
119 Run(BuildArgs),
120
121 #[command(visible_alias = "t")]
123 #[command(long_about = "\
124Execute all unit and integration tests and build examples of a local package.
125
126For cross-compilation targets, QEMU user-mode emulation is used to run tests.")]
127 Test(BuildArgs),
128
129 #[command(long_about = "\
131Execute all benchmarks of a local package.
132
133For cross-compilation targets, QEMU user-mode emulation is used to run benchmarks.")]
134 Bench(BuildArgs),
135
136 #[command(long_about = "\
138Display all supported cross-compilation targets.
139
140You can also use glob patterns with --target to match multiple targets,
141for example: --target '*-linux-musl' or --target 'aarch64-*'")]
142 Targets(TargetsArgs),
143
144 Version,
146}
147
148#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum)]
150pub enum OutputFormat {
151 #[default]
153 Text,
154 Json,
156 Plain,
158}
159
160#[derive(ClapArgs, Debug, Clone, Default)]
161pub struct TargetsArgs {
162 #[arg(
164 short = 'f',
165 long = "format",
166 value_enum,
167 default_value = "text",
168 help = "Output format (text, json, plain)"
169 )]
170 pub format: OutputFormat,
171}
172
173#[derive(ClapArgs, Debug, Clone, Default)]
174#[command(next_help_heading = "Target Selection")]
175pub struct BuildArgs {
176 #[arg(
179 short = 't',
180 long = "target",
181 visible_alias = "targets",
182 value_delimiter = ',',
183 env = "TARGETS",
184 value_name = "TRIPLE",
185 help = "Build for the target triple(s), comma-separated",
186 long_help = "\
187Build for the specified target architecture. This flag may be specified multiple
188times or with comma-separated values. Supports glob patterns like '*-linux-musl'.
189The general format of the triple is <arch><sub>-<vendor>-<sys>-<abi>.
190Run the 'targets' subcommand to see all supported targets.
191
192Examples: -t x86_64-unknown-linux-musl, -t '*-linux-musl'"
193 )]
194 pub targets: Vec<String>,
195
196 #[arg(
199 short = 'F',
200 long,
201 env = "FEATURES",
202 value_name = "FEATURES",
203 conflicts_with = "all_features",
204 help_heading = "Feature Selection",
205 long_help = "\
206Space or comma separated list of features to activate. Features of workspace members
207may be enabled with package-name/feature-name syntax. May be specified multiple times."
208 )]
209 pub features: Option<String>,
210
211 #[arg(long, env = "NO_DEFAULT_FEATURES", help_heading = "Feature Selection")]
213 pub no_default_features: bool,
214
215 #[arg(
217 long,
218 env = "ALL_FEATURES",
219 conflicts_with = "features",
220 help_heading = "Feature Selection"
221 )]
222 pub all_features: bool,
223
224 #[arg(
227 short = 'r',
228 long = "release",
229 conflicts_with = "profile",
230 help_heading = "Profile",
231 long_help = "\
232Build artifacts in release mode, with optimizations. Equivalent to --profile=release."
233 )]
234 pub release: bool,
235
236 #[arg(
238 long,
239 default_value = "release",
240 env = "PROFILE",
241 value_name = "PROFILE-NAME",
242 conflicts_with = "release",
243 help_heading = "Profile",
244 long_help = "\
245Build artifacts with the specified profile. Built-in: dev, release, test, bench.
246Custom profiles can be defined in Cargo.toml. Default is 'release' (differs from cargo's 'dev')."
247 )]
248 pub profile: String,
249
250 #[arg(
253 short = 'p',
254 long,
255 env = "PACKAGE",
256 value_name = "SPEC",
257 help_heading = "Package Selection",
258 long_help = "\
259Build only the specified packages. This flag may be specified multiple times
260and supports common Unix glob patterns like *, ?, and []."
261 )]
262 pub package: Option<String>,
263
264 #[arg(
266 long,
267 visible_alias = "all",
268 env = "BUILD_WORKSPACE",
269 help_heading = "Package Selection"
270 )]
271 pub workspace: bool,
272
273 #[arg(
275 long,
276 env = "EXCLUDE",
277 value_name = "SPEC",
278 requires = "workspace",
279 help_heading = "Package Selection",
280 long_help = "\
281Exclude the specified packages. Must be used in conjunction with the --workspace flag.
282This flag may be specified multiple times and supports common Unix glob patterns."
283 )]
284 pub exclude: Option<String>,
285
286 #[arg(
288 long = "bin",
289 env = "BIN_TARGET",
290 value_name = "NAME",
291 help_heading = "Package Selection",
292 long_help = "\
293Build the specified binary. This flag may be specified multiple times
294and supports common Unix glob patterns."
295 )]
296 pub bin_target: Option<String>,
297
298 #[arg(long = "bins", env = "BUILD_BINS", help_heading = "Package Selection")]
300 pub build_bins: bool,
301
302 #[arg(long = "lib", env = "BUILD_LIB", help_heading = "Package Selection")]
304 pub build_lib: bool,
305
306 #[arg(
308 long = "example",
309 env = "EXAMPLE_TARGET",
310 value_name = "NAME",
311 help_heading = "Package Selection",
312 long_help = "\
313Build the specified example. This flag may be specified multiple times
314and supports common Unix glob patterns."
315 )]
316 pub example_target: Option<String>,
317
318 #[arg(
320 long = "examples",
321 env = "BUILD_EXAMPLES",
322 help_heading = "Package Selection"
323 )]
324 pub build_examples: bool,
325
326 #[arg(
328 long = "test",
329 env = "TEST_TARGET",
330 value_name = "NAME",
331 help_heading = "Package Selection",
332 long_help = "\
333Build the specified integration test. This flag may be specified multiple times
334and supports common Unix glob patterns."
335 )]
336 pub test_target: Option<String>,
337
338 #[arg(
340 long = "tests",
341 env = "BUILD_TESTS",
342 help_heading = "Package Selection",
343 long_help = "\
344Build all targets that have the test = true manifest flag set. By default this
345includes the library and binaries built as unittests, and integration tests."
346 )]
347 pub build_tests: bool,
348
349 #[arg(
351 long = "bench",
352 env = "BENCH_TARGET",
353 value_name = "NAME",
354 help_heading = "Package Selection",
355 long_help = "\
356Build the specified benchmark. This flag may be specified multiple times
357and supports common Unix glob patterns."
358 )]
359 pub bench_target: Option<String>,
360
361 #[arg(
363 long = "benches",
364 env = "BUILD_BENCHES",
365 help_heading = "Package Selection",
366 long_help = "\
367Build all targets that have the bench = true manifest flag set. By default this
368includes the library and binaries built as benchmarks, and bench targets."
369 )]
370 pub build_benches: bool,
371
372 #[arg(
374 long = "all-targets",
375 env = "BUILD_ALL_TARGETS",
376 help_heading = "Package Selection"
377 )]
378 pub build_all_targets: bool,
379
380 #[arg(long, env = "MANIFEST_PATH", value_name = "PATH",
382 value_hint = ValueHint::FilePath, help_heading = "Package Selection",
383 long_help = "\
384Path to Cargo.toml. By default, Cargo searches for the Cargo.toml file
385in the current directory or any parent directory.")]
386 pub manifest_path: Option<PathBuf>,
387
388 #[arg(long, default_value = DEFAULT_GLIBC_VERSION, env = "GLIBC_VERSION",
391 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
392 pub glibc_version: String,
393
394 #[arg(long, default_value = DEFAULT_IPHONE_SDK_VERSION, env = "IPHONE_SDK_VERSION",
396 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
397 pub iphone_sdk_version: String,
398
399 #[arg(long, env = "IPHONE_SDK_PATH", value_name = "PATH",
401 value_hint = ValueHint::DirPath, help_heading = "Toolchain Versions",
402 long_help = "\
403Override iPhoneOS SDK path for device targets. Skips version lookup.")]
404 pub iphone_sdk_path: Option<PathBuf>,
405
406 #[arg(long, env = "IPHONE_SIMULATOR_SDK_PATH", value_name = "PATH",
408 value_hint = ValueHint::DirPath, help_heading = "Toolchain Versions",
409 long_help = "\
410Override iPhoneSimulator SDK path for simulator targets. Skips version lookup.")]
411 pub iphone_simulator_sdk_path: Option<PathBuf>,
412
413 #[arg(long, default_value = DEFAULT_MACOS_SDK_VERSION, env = "MACOS_SDK_VERSION",
415 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
416 pub macos_sdk_version: String,
417
418 #[arg(long, env = "MACOS_SDK_PATH", value_name = "PATH",
420 value_hint = ValueHint::DirPath, help_heading = "Toolchain Versions",
421 long_help = "\
422Override macOS SDK path directly. Skips version lookup.")]
423 pub macos_sdk_path: Option<PathBuf>,
424
425 #[arg(long, default_value = DEFAULT_FREEBSD_VERSION, env = "FREEBSD_VERSION",
427 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions")]
428 pub freebsd_version: String,
429
430 #[arg(long, default_value = DEFAULT_NDK_VERSION, env = "NDK_VERSION",
432 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions",
433 long_help = "\
434Specify Android NDK version for Android targets. Auto-downloaded from Google's official repository.")]
435 pub ndk_version: String,
436
437 #[arg(long, default_value = DEFAULT_QEMU_VERSION, env = "QEMU_VERSION",
439 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions",
440 long_help = "\
441Specify QEMU version for user-mode emulation. Used to run cross-compiled binaries during test/run/bench.")]
442 pub qemu_version: String,
443
444 #[arg(long, default_value = DEFAULT_CROSS_MAKE_VERSION, env = "CROSS_MAKE_VERSION",
446 value_name = "VERSION", hide_default_value = true, help_heading = "Toolchain Versions",
447 long_help = "\
448Specify cross-compiler make version. This determines which version of cross-compilation \
449toolchains will be downloaded from the upstream repository.")]
450 pub cross_make_version: String,
451
452 #[arg(long, env = "CROSS_COMPILER_DIR", value_name = "DIR",
455 value_hint = ValueHint::DirPath, help_heading = "Directories",
456 long_help = "\
457Directory where cross-compiler toolchains will be downloaded and stored. Defaults to temp dir.
458Set this to reuse downloaded toolchains across builds.")]
459 pub cross_compiler_dir: Option<PathBuf>,
460
461 #[arg(long, visible_alias = "target-dir", env = "CARGO_TARGET_DIR", value_name = "DIR",
463 value_hint = ValueHint::DirPath, help_heading = "Directories",
464 long_help = "\
465Directory for all generated artifacts and intermediate files. Defaults to 'target'.")]
466 pub cargo_target_dir: Option<PathBuf>,
467
468 #[arg(long, env = "ARTIFACT_DIR", value_name = "DIR",
470 value_hint = ValueHint::DirPath, help_heading = "Directories",
471 long_help = "\
472Copy final artifacts to this directory. Unstable, requires nightly toolchain.")]
473 pub artifact_dir: Option<PathBuf>,
474
475 #[arg(long, env = "CC", value_name = "PATH",
478 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
479 long_help = "\
480Override the C compiler path. By default, the appropriate cross-compiler is auto-configured.")]
481 pub cc: Option<PathBuf>,
482
483 #[arg(long, env = "CXX", value_name = "PATH",
485 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
486 long_help = "\
487Override the C++ compiler path. By default, the appropriate cross-compiler is auto-configured.")]
488 pub cxx: Option<PathBuf>,
489
490 #[arg(long, env = "AR", value_name = "PATH",
492 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
493 long_help = "\
494Override the archiver (ar) path. By default, the appropriate archiver is auto-configured.")]
495 pub ar: Option<PathBuf>,
496
497 #[arg(long, env = "LINKER", value_name = "PATH",
499 value_hint = ValueHint::ExecutablePath, help_heading = "Compiler Options",
500 long_help = "\
501Override the linker path. By default, the cross-compiler is used as linker.
502This option takes precedence over auto-configured linker.")]
503 pub linker: Option<PathBuf>,
504
505 #[arg(
507 long,
508 env = "CFLAGS",
509 value_name = "FLAGS",
510 allow_hyphen_values = true,
511 help_heading = "Compiler Options",
512 long_help = "\
513Additional flags to pass to the C compiler. Appended to default CFLAGS.
514Example: --cflags '-O2 -Wall -march=native'"
515 )]
516 pub cflags: Option<String>,
517
518 #[arg(
520 long,
521 env = "CXXFLAGS",
522 value_name = "FLAGS",
523 allow_hyphen_values = true,
524 help_heading = "Compiler Options",
525 long_help = "\
526Additional flags to pass to the C++ compiler. Appended to default CXXFLAGS.
527Example: --cxxflags '-O2 -Wall -std=c++17'"
528 )]
529 pub cxxflags: Option<String>,
530
531 #[arg(
533 long,
534 env = "LDFLAGS",
535 value_name = "FLAGS",
536 allow_hyphen_values = true,
537 help_heading = "Compiler Options",
538 long_help = "\
539Additional flags to pass to the linker. Appended to default LDFLAGS.
540Example: --ldflags '-L/usr/local/lib -static'"
541 )]
542 pub ldflags: Option<String>,
543
544 #[arg(
546 long,
547 env = "CXXSTDLIB",
548 value_name = "LIB",
549 help_heading = "Compiler Options",
550 long_help = "\
551Specify the C++ standard library to use (libc++, libstdc++, etc)."
552 )]
553 pub cxxstdlib: Option<String>,
554
555 #[arg(
557 long,
558 short = 'G',
559 env = "CMAKE_GENERATOR",
560 value_name = "GENERATOR",
561 help_heading = "Compiler Options",
562 long_help = "\
563Specify the CMake generator to use. On Windows, this overrides the auto-detection.
564Common generators: Ninja, 'MinGW Makefiles', 'Unix Makefiles', 'NMake Makefiles'.
565If not specified, auto-detects: Ninja > MinGW Makefiles > Unix Makefiles."
566 )]
567 pub cmake_generator: Option<String>,
568
569 #[arg(long = "rustflag", visible_alias = "rustflags", value_name = "FLAG",
571 env = "ADDITIONAL_RUSTFLAGS", allow_hyphen_values = true,
572 action = clap::ArgAction::Append, help_heading = "Compiler Options",
573 long_help = "\
574Additional flags to pass to rustc via RUSTFLAGS. Can be specified multiple times.
575Example: --rustflag '-C target-cpu=native' --rustflag '-C lto=thin'")]
576 pub rustflags: Vec<String>,
577
578 #[arg(long, env = "RUSTC_WRAPPER", value_name = "PATH",
580 value_hint = ValueHint::ExecutablePath,
581 conflicts_with = "enable_sccache", help_heading = "Compiler Options",
582 long_help = "\
583Specify a rustc wrapper program (sccache, cachepot, etc) for compilation caching.")]
584 pub rustc_wrapper: Option<PathBuf>,
585
586 #[arg(
588 long,
589 env = "NO_TOOLCHAIN_SETUP",
590 help_heading = "Compiler Options",
591 long_help = "\
592Skip downloading and configuring cross-compilation toolchain.
593Use this when you have pre-configured system compilers or want to use
594only CLI-provided compiler options (--cc, --cxx, --ar, --linker)."
595 )]
596 pub no_toolchain_setup: bool,
597
598 #[arg(
601 long,
602 env = "ENABLE_SCCACHE",
603 conflicts_with = "rustc_wrapper",
604 help_heading = "Sccache Options",
605 long_help = "\
606Enable sccache as the rustc wrapper for compilation caching.
607Speeds up compilation by caching previous compilations."
608 )]
609 pub enable_sccache: bool,
610
611 #[arg(long, env = "SCCACHE_DIR", value_name = "DIR",
613 value_hint = ValueHint::DirPath, help_heading = "Sccache Options",
614 long_help = "\
615Directory for sccache's local disk cache. Defaults to $HOME/.cache/sccache.")]
616 pub sccache_dir: Option<PathBuf>,
617
618 #[arg(
620 long,
621 env = "SCCACHE_CACHE_SIZE",
622 value_name = "SIZE",
623 help_heading = "Sccache Options",
624 long_help = "\
625Maximum size of the local disk cache (e.g., '10G', '500M'). Default is 10GB."
626 )]
627 pub sccache_cache_size: Option<String>,
628
629 #[arg(
631 long,
632 env = "SCCACHE_IDLE_TIMEOUT",
633 value_name = "SECONDS",
634 help_heading = "Sccache Options",
635 long_help = "\
636Idle timeout in seconds for the sccache server. Set to 0 to run indefinitely."
637 )]
638 pub sccache_idle_timeout: Option<String>,
639
640 #[arg(
642 long,
643 env = "SCCACHE_LOG",
644 value_name = "LEVEL",
645 help_heading = "Sccache Options",
646 long_help = "\
647Log level for sccache. Valid: error, warn, info, debug, trace"
648 )]
649 pub sccache_log: Option<String>,
650
651 #[arg(
653 long,
654 env = "SCCACHE_NO_DAEMON",
655 help_heading = "Sccache Options",
656 long_help = "\
657Run sccache without daemon (single-process mode). May be slower but avoids daemon startup issues."
658 )]
659 pub sccache_no_daemon: bool,
660
661 #[arg(
663 long,
664 env = "SCCACHE_DIRECT",
665 help_heading = "Sccache Options",
666 long_help = "\
667Enable sccache direct mode. Caches based on source file content directly, bypassing preprocessor."
668 )]
669 pub sccache_direct: bool,
670
671 #[arg(
674 long,
675 env = "CRATE_CC_NO_DEFAULTS",
676 hide = true,
677 help_heading = "CC Crate Options"
678 )]
679 pub cc_no_defaults: bool,
680
681 #[arg(
683 long,
684 env = "CC_SHELL_ESCAPED_FLAGS",
685 hide = true,
686 help_heading = "CC Crate Options"
687 )]
688 pub cc_shell_escaped_flags: bool,
689
690 #[arg(
692 long,
693 env = "CC_ENABLE_DEBUG_OUTPUT",
694 hide = true,
695 help_heading = "CC Crate Options"
696 )]
697 pub cc_enable_debug: bool,
698
699 #[arg(long, value_parser = parse_optional_bool, env = "CRT_STATIC",
702 value_name = "BOOL", num_args = 0..=1, default_missing_value = "true",
703 help_heading = "Build Options",
704 long_help = "\
705Control whether the C runtime is statically linked. true=static (larger, portable),
706false=dynamic (smaller, requires libc). Musl defaults to static, glibc to dynamic.")]
707 pub crt_static: Option<bool>,
708
709 #[arg(
711 long,
712 env = "PANIC_IMMEDIATE_ABORT",
713 help_heading = "Build Options",
714 long_help = "\
715Use panic=abort and remove panic formatting code for smaller binaries.
716Requires nightly and implies --build-std. Stack traces will not be available."
717 )]
718 pub panic_immediate_abort: bool,
719
720 #[arg(long, value_name = "MODE", hide = true, help_heading = "Build Options")]
722 pub fmt_debug: Option<String>,
723
724 #[arg(long, value_name = "MODE", hide = true, help_heading = "Build Options")]
726 pub location_detail: Option<String>,
727
728 #[arg(long, value_parser = parse_build_std, env = "BUILD_STD",
730 value_name = "CRATES", help_heading = "Build Options",
731 num_args = 0..=1, default_missing_value = "true",
732 long_help = "\
733Build the standard library from source (requires nightly). Without arguments, builds 'std'.
734Use 'true' for full std or specify crates like 'core,alloc'. Required for unsupported targets or panic=abort.")]
735 pub build_std: Option<String>,
736
737 #[arg(
739 long,
740 env = "BUILD_STD_FEATURES",
741 value_name = "FEATURES",
742 requires = "build_std",
743 help_heading = "Build Options",
744 long_help = "\
745Space-separated features for std. Common: panic_immediate_abort, optimize_for_size"
746 )]
747 pub build_std_features: Option<String>,
748
749 #[arg(
751 long,
752 visible_alias = "trim-paths",
753 env = "CARGO_TRIM_PATHS",
754 value_name = "VALUE",
755 num_args = 0..=1,
756 default_missing_value = "true",
757 help_heading = "Build Options",
758 long_help = "\
759Control how paths are trimmed in compiler output for reproducible builds.
760Valid: true, macro, diagnostics, object, all, none (default: false)"
761 )]
762 pub cargo_trim_paths: Option<String>,
763
764 #[arg(
766 long,
767 env = "NO_EMBED_METADATA",
768 hide = true,
769 help_heading = "Build Options"
770 )]
771 pub no_embed_metadata: bool,
772
773 #[arg(
775 long,
776 env = "RUSTC_BOOTSTRAP",
777 value_name = "VALUE",
778 num_args = 0..=1,
779 default_missing_value = "1",
780 hide = true,
781 help_heading = "Build Options"
782 )]
783 pub rustc_bootstrap: Option<String>,
784
785 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count,
788 env = "VERBOSE_LEVEL", conflicts_with = "quiet",
789 help_heading = "Output Options",
790 long_help = "\
791Use verbose output. -v=commands/warnings, -vv=+deps/build scripts, -vvv=max verbosity")]
792 pub verbose_level: u8,
793
794 #[arg(
796 short = 'q',
797 long,
798 env = "QUIET",
799 conflicts_with = "verbose_level",
800 help_heading = "Output Options",
801 long_help = "\
802Do not print cargo log messages. Shows only errors and warnings."
803 )]
804 pub quiet: bool,
805
806 #[arg(
808 long,
809 env = "MESSAGE_FORMAT",
810 value_name = "FMT",
811 help_heading = "Output Options",
812 long_help = "\
813Output format for diagnostics. Valid: human (default), short, json"
814 )]
815 pub message_format: Option<String>,
816
817 #[arg(
819 long,
820 env = "COLOR",
821 value_name = "WHEN",
822 help_heading = "Output Options",
823 long_help = "\
824Control when colored output is used. Valid: auto (default), always, never"
825 )]
826 pub color: Option<String>,
827
828 #[arg(long, env = "BUILD_PLAN", hide = true, help_heading = "Output Options")]
830 pub build_plan: bool,
831
832 #[arg(long, env = "TIMINGS", value_name = "FMTS",
834 num_args = 0..=1, default_missing_value = "true",
835 help_heading = "Output Options",
836 long_help = "\
837Output timing information. --timings=HTML report, --timings=json for JSON. Saved to target/cargo-timings/.")]
838 pub timings: Option<String>,
839
840 #[arg(
843 long,
844 env = "IGNORE_RUST_VERSION",
845 help_heading = "Dependency Options",
846 long_help = "\
847Ignore rust-version specification in packages. Allows building with older Rust versions."
848 )]
849 pub ignore_rust_version: bool,
850
851 #[arg(
853 long,
854 env = "LOCKED",
855 help_heading = "Dependency Options",
856 long_help = "\
857Assert Cargo.lock will remain unchanged. Exits with error if missing or needs updating. Use in CI."
858 )]
859 pub locked: bool,
860
861 #[arg(
863 long,
864 env = "OFFLINE",
865 help_heading = "Dependency Options",
866 long_help = "\
867Prevent network access. Uses locally cached data. Run 'cargo fetch' first if needed."
868 )]
869 pub offline: bool,
870
871 #[arg(
873 long,
874 env = "FROZEN",
875 help_heading = "Dependency Options",
876 long_help = "\
877Equivalent to --locked --offline. Requires Cargo.lock and cache are up to date."
878 )]
879 pub frozen: bool,
880
881 #[arg(long, env = "LOCKFILE_PATH", value_name = "PATH",
883 value_hint = ValueHint::FilePath, help_heading = "Dependency Options",
884 long_help = "\
885Override lockfile path from default (<workspace_root>/Cargo.lock). Requires nightly.")]
886 pub lockfile_path: Option<PathBuf>,
887
888 #[arg(
891 short = 'j',
892 long,
893 env = "JOBS",
894 value_name = "N",
895 help_heading = "Build Configuration",
896 long_help = "\
897Number of parallel jobs. Defaults to logical CPUs. Negative=CPUs+N. 'default' to reset."
898 )]
899 pub jobs: Option<String>,
900
901 #[arg(
903 long,
904 env = "KEEP_GOING",
905 help_heading = "Build Configuration",
906 long_help = "\
907Build as many crates in the dependency graph as possible. Rather than aborting on the first
908crate that fails to build, continue with other crates in the dependency graph."
909 )]
910 pub keep_going: bool,
911
912 #[arg(
914 long,
915 env = "FUTURE_INCOMPAT_REPORT",
916 help_heading = "Build Configuration",
917 long_help = "\
918Displays a future-incompat report for any future-incompatible warnings produced during
919execution of this command. See 'cargo report' for more information."
920 )]
921 pub future_incompat_report: bool,
922
923 #[arg(
927 long,
928 visible_alias = "args",
929 value_name = "ARGS",
930 hide = true,
931 allow_hyphen_values = true,
932 action = clap::ArgAction::Append,
933 help_heading = "Additional Options"
934 )]
935 pub cargo_args: Vec<String>,
936
937 #[arg(short = 'Z', value_name = "FLAG",
939 action = clap::ArgAction::Append, help_heading = "Additional Options",
940 long_help = "\
941Unstable (nightly-only) flags to Cargo. Run 'cargo -Z help' for details on available flags.
942Common flags: build-std, unstable-options")]
943 pub cargo_z_flags: Vec<String>,
944
945 #[arg(long = "config", value_name = "KEY=VALUE",
947 action = clap::ArgAction::Append, help_heading = "Additional Options",
948 long_help = "\
949Override a Cargo configuration value. The argument should be in TOML syntax of KEY=VALUE.
950This flag may be specified multiple times.
951Example: --config 'build.jobs=4' --config 'profile.release.lto=true'")]
952 pub cargo_config: Vec<String>,
953
954 #[arg(short = 'C', long = "directory", env = "CARGO_CWD",
956 value_name = "DIR", value_hint = ValueHint::DirPath,
957 help_heading = "Additional Options",
958 long_help = "\
959Changes the current working directory before executing any specified operations.
960This affects where cargo looks for the project manifest (Cargo.toml) and .cargo/config.toml.")]
961 pub cargo_cwd: Option<PathBuf>,
962
963 #[arg(
965 long = "toolchain",
966 env = "TOOLCHAIN",
967 value_name = "TOOLCHAIN",
968 help_heading = "Additional Options",
969 long_help = "\
970Specify the Rust toolchain to use for compilation. This is an alternative to the +toolchain
971syntax (e.g., +nightly). Examples: --toolchain nightly, --toolchain stable, --toolchain 1.75.0"
972 )]
973 pub toolchain_option: Option<String>,
974
975 #[arg(long, visible_alias = "github-proxy-mirror", env = "GH_PROXY", value_name = "URL",
977 value_hint = ValueHint::Url, hide_env = true,
978 help_heading = "Additional Options",
979 long_help = "\
980Specify a GitHub mirror/proxy URL for downloading cross-compiler toolchains.
981Useful in regions where GitHub access is slow or restricted.
982Example: --github-proxy 'https://ghproxy.com/'")]
983 pub github_proxy: Option<String>,
984
985 #[arg(
987 long,
988 env = "CLEAN_CACHE",
989 help_heading = "Additional Options",
990 long_help = "\
991Clean the target directory before building. Equivalent to running 'cargo clean' before the build."
992 )]
993 pub clean_cache: bool,
994
995 #[arg(
998 last = true,
999 allow_hyphen_values = true,
1000 value_name = "ARGS",
1001 help = "Arguments passed through to the underlying cargo command",
1002 long_help = "\
1003Arguments passed through to the underlying cargo command. Everything after -- is passed
1004directly to cargo/test runner. For test command, these are passed to the test binary.
1005Examples: test -- --nocapture --test-threads=1, run -- --arg1 --arg2"
1006 )]
1007 pub passthrough_args: Vec<String>,
1008}
1009
1010impl BuildArgs {
1011 #[must_use]
1013 pub fn default_for_host() -> Self {
1014 Self {
1015 profile: "release".to_string(),
1016 glibc_version: DEFAULT_GLIBC_VERSION.to_string(),
1017 iphone_sdk_version: DEFAULT_IPHONE_SDK_VERSION.to_string(),
1018 macos_sdk_version: DEFAULT_MACOS_SDK_VERSION.to_string(),
1019 freebsd_version: DEFAULT_FREEBSD_VERSION.to_string(),
1020 ndk_version: DEFAULT_NDK_VERSION.to_string(),
1021 qemu_version: DEFAULT_QEMU_VERSION.to_string(),
1022 cross_make_version: DEFAULT_CROSS_MAKE_VERSION.to_string(),
1023 ..Default::default()
1024 }
1025 }
1026}
1027
1028fn parse_optional_bool(s: &str) -> std::result::Result<bool, String> {
1030 match s.to_lowercase().as_str() {
1031 "true" | "1" | "yes" => Ok(true),
1032 "false" | "0" | "no" => Ok(false),
1033 _ => Err(format!("invalid bool value: {s}")),
1034 }
1035}
1036
1037fn parse_build_std(s: &str) -> std::result::Result<String, String> {
1039 match s.to_lowercase().as_str() {
1040 "false" | "0" | "no" | "" => Ok(String::new()), "true" | "1" | "yes" => Ok("true".to_string()),
1042 _ => Ok(s.to_string()),
1043 }
1044}
1045
1046#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1048pub enum Command {
1049 #[default]
1050 Build,
1051 Check,
1052 Run,
1053 Test,
1054 Bench,
1055}
1056
1057impl Command {
1058 #[must_use]
1059 pub const fn as_str(&self) -> &'static str {
1060 match self {
1061 Self::Build => "build",
1062 Self::Check => "check",
1063 Self::Run => "run",
1064 Self::Test => "test",
1065 Self::Bench => "bench",
1066 }
1067 }
1068
1069 #[must_use]
1070 pub const fn needs_runner(&self) -> bool {
1071 matches!(self, Self::Run | Self::Test | Self::Bench)
1072 }
1073}
1074
1075#[derive(Debug, Clone)]
1077pub struct Args {
1078 pub toolchain: Option<String>,
1080 pub command: Command,
1082 pub targets: Vec<String>,
1084 pub no_cargo_target: bool,
1086 pub cross_make_version: String,
1088 pub cross_compiler_dir: PathBuf,
1090 pub build: BuildArgs,
1092}
1093
1094impl std::ops::Deref for Args {
1095 type Target = BuildArgs;
1096
1097 fn deref(&self) -> &Self::Target {
1098 &self.build
1099 }
1100}
1101
1102impl std::ops::DerefMut for Args {
1103 fn deref_mut(&mut self) -> &mut Self::Target {
1104 &mut self.build
1105 }
1106}
1107
1108impl Args {
1109 fn from_build_args(b: BuildArgs, command: Command, toolchain: Option<String>) -> Result<Self> {
1111 let cross_compiler_dir = b
1112 .cross_compiler_dir
1113 .clone()
1114 .unwrap_or_else(|| std::env::temp_dir().join("rust-cross-compiler"));
1115 let targets = expand_target_list(&b.targets)?;
1116
1117 Ok(Self {
1118 toolchain,
1119 command,
1120 targets,
1121 no_cargo_target: false,
1122 cross_make_version: b.cross_make_version.clone(),
1123 cross_compiler_dir,
1124 build: b,
1125 })
1126 }
1127}
1128
1129pub enum ParseResult {
1131 Build(Box<Args>),
1133 ShowTargets(OutputFormat),
1135 ShowVersion,
1137}
1138
1139fn sanitize_clap_env() {
1143 let empty_vars: Vec<_> = std::env::vars()
1144 .filter(|(_, v)| v.is_empty())
1145 .map(|(k, _)| k)
1146 .collect();
1147
1148 for var in empty_vars {
1149 std::env::remove_var(&var);
1150 }
1151}
1152
1153pub fn parse_args() -> Result<ParseResult> {
1155 let args: Vec<String> = std::env::args().collect();
1156 parse_args_from(args)
1157}
1158
1159pub fn parse_args_from(args: Vec<String>) -> Result<ParseResult> {
1161 use std::env;
1162
1163 sanitize_clap_env();
1166
1167 let mut toolchain: Option<String> = None;
1168
1169 let is_cargo_subcommand = env::var("CARGO").is_ok()
1173 && env::var("CARGO_HOME").is_ok()
1174 && args.get(1).map(String::as_str) == Some(SUBCOMMAND);
1175
1176 let skip_count = if is_cargo_subcommand { 2 } else { 1 };
1177 let mut args: Vec<String> = args.iter().skip(skip_count).cloned().collect();
1178
1179 if let Some(tc) = args.first().and_then(|a| a.strip_prefix('+')) {
1182 toolchain = Some(tc.to_string());
1183 args.remove(0);
1184 }
1185
1186 args.insert(0, BIN_NAME.to_string());
1188
1189 let cmd = build_command_with_dynamic_help();
1191
1192 let cli = match cmd.try_get_matches_from(&args) {
1194 Ok(matches) => {
1195 Cli::from_arg_matches(&matches).map_err(|e| CrossError::ClapError(e.to_string()))?
1196 }
1197 Err(e) => {
1198 if matches!(
1200 e.kind(),
1201 clap::error::ErrorKind::DisplayHelp
1202 | clap::error::ErrorKind::DisplayVersion
1203 | clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
1204 ) {
1205 e.exit();
1206 }
1207 return Err(CrossError::ClapError(e.render().to_string()));
1209 }
1210 };
1211
1212 process_cli(cli, toolchain)
1213}
1214
1215fn build_command_with_dynamic_help() -> clap::Command {
1217 let prog = program_name();
1218
1219 let usage = format!("{prog} [+toolchain] <COMMAND> [OPTIONS]");
1221 let after_help = format!(
1222 "Use '{prog} <COMMAND> --help' for more information about a command.\n\n\
1223TOOLCHAIN:\n \
1224 If the first argument begins with +, it will be interpreted as a Rust toolchain\n \
1225 name (such as +nightly, +stable, or +1.75.0). This follows the same convention\n \
1226 as rustup and cargo.\n\n\
1227EXAMPLES:\n \
1228 {prog} build -t x86_64-unknown-linux-musl\n \
1229 {prog} +nightly build -t aarch64-unknown-linux-gnu --profile release\n \
1230 {prog} build -t '*-linux-musl' --crt-static true\n \
1231 {prog} test -t x86_64-unknown-linux-musl -- --nocapture"
1232 );
1233
1234 let glibc_help = format!(
1236 "Specify glibc version for GNU libc targets. The version determines the minimum Linux kernel\n\
1237 version required. Lower versions provide better compatibility with older systems.\n\
1238 Supported: {}",
1239 supported_glibc_versions_str()
1240 );
1241 let freebsd_help = format!(
1242 "Specify FreeBSD version for FreeBSD targets. Supported: {}",
1243 supported_freebsd_versions_str()
1244 );
1245 let iphone_sdk_help = format!(
1246 "Specify iPhone SDK version for iOS targets. On Linux: uses pre-built SDK from releases.\n\
1247 On macOS: uses installed Xcode SDK. Supported on Linux: {}",
1248 supported_iphone_sdk_versions_str()
1249 );
1250 let macos_sdk_help = format!(
1251 "Specify macOS SDK version for Darwin targets. On Linux: uses osxcross with pre-built SDK.\n\
1252 On macOS: uses installed Xcode SDK. Supported on Linux: {}",
1253 supported_macos_sdk_versions_str()
1254 );
1255
1256 let mut cmd = Cli::command().override_usage(usage).after_help(after_help);
1258
1259 for subcmd_name in &["build", "check", "run", "test", "bench"] {
1261 let glibc_help = glibc_help.clone();
1262 let freebsd_help = freebsd_help.clone();
1263 let iphone_sdk_help = iphone_sdk_help.clone();
1264 let macos_sdk_help = macos_sdk_help.clone();
1265 cmd = cmd.mut_subcommand(*subcmd_name, |subcmd| {
1266 subcmd
1267 .override_usage(format!(
1268 "{prog} [+toolchain] {subcmd_name} [OPTIONS] [-- <PASSTHROUGH_ARGS>...]"
1269 ))
1270 .mut_arg("glibc_version", |arg| arg.long_help(glibc_help))
1271 .mut_arg("freebsd_version", |arg| arg.long_help(freebsd_help))
1272 .mut_arg("iphone_sdk_version", |arg| arg.long_help(iphone_sdk_help))
1273 .mut_arg("macos_sdk_version", |arg| arg.long_help(macos_sdk_help))
1274 });
1275 }
1276
1277 cmd
1278}
1279
1280fn process_cli(cli: Cli, toolchain: Option<String>) -> Result<ParseResult> {
1281 match cli.command {
1282 CliCommand::Build(args) => {
1283 let args = finalize_args(args, Command::Build, toolchain)?;
1284 Ok(ParseResult::Build(Box::new(args)))
1285 }
1286 CliCommand::Check(args) => {
1287 let args = finalize_args(args, Command::Check, toolchain)?;
1288 Ok(ParseResult::Build(Box::new(args)))
1289 }
1290 CliCommand::Run(args) => {
1291 let args = finalize_args(args, Command::Run, toolchain)?;
1292 Ok(ParseResult::Build(Box::new(args)))
1293 }
1294 CliCommand::Test(args) => {
1295 let args = finalize_args(args, Command::Test, toolchain)?;
1296 Ok(ParseResult::Build(Box::new(args)))
1297 }
1298 CliCommand::Bench(args) => {
1299 let args = finalize_args(args, Command::Bench, toolchain)?;
1300 Ok(ParseResult::Build(Box::new(args)))
1301 }
1302 CliCommand::Targets(args) => Ok(ParseResult::ShowTargets(args.format)),
1303 CliCommand::Version => Ok(ParseResult::ShowVersion),
1304 }
1305}
1306
1307fn is_glob_pattern(s: &str) -> bool {
1309 s.contains('*') || s.contains('?') || s.contains('[')
1310}
1311
1312fn validate_target_triple(target: &str) -> Result<()> {
1314 for c in target.chars() {
1315 if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '-' && c != '_' {
1316 return Err(CrossError::InvalidTargetTriple {
1317 target: target.to_string(),
1318 char: c,
1319 });
1320 }
1321 }
1322 Ok(())
1323}
1324
1325fn expand_target_list(targets: &[String]) -> Result<Vec<String>> {
1327 let mut result = Vec::new();
1328 for target in targets {
1329 for part in target.split([',', '\n']) {
1331 let part = part.trim();
1332 if part.is_empty() {
1333 continue;
1334 }
1335 let expanded = config::expand_targets(part);
1336 if expanded.is_empty() {
1337 if is_glob_pattern(part) {
1339 return Err(CrossError::NoMatchingTargets {
1340 pattern: part.to_string(),
1341 });
1342 }
1343 validate_target_triple(part)?;
1345 if !result.contains(&part.to_string()) {
1346 result.push(part.to_string());
1347 }
1348 } else {
1349 for t in expanded {
1350 let t = t.to_string();
1351 if !result.contains(&t) {
1352 result.push(t);
1353 }
1354 }
1355 }
1356 }
1357 }
1358 Ok(result)
1359}
1360
1361fn finalize_args(
1362 mut build_args: BuildArgs,
1363 command: Command,
1364 toolchain: Option<String>,
1365) -> Result<Args> {
1366 if build_args.release {
1368 build_args.profile = "release".to_string();
1369 }
1370
1371 if build_args
1373 .build_std
1374 .as_ref()
1375 .is_some_and(std::string::String::is_empty)
1376 {
1377 build_args.build_std = None;
1378 }
1379
1380 if build_args.cargo_args.is_empty() {
1382 if let Some(env_args) = parse_env_args("CARGO_ARGS") {
1383 build_args.cargo_args = env_args;
1384 }
1385 }
1386 if build_args.passthrough_args.is_empty() {
1387 if let Some(env_args) = parse_env_args("CARGO_PASSTHROUGH_ARGS") {
1388 build_args.passthrough_args = env_args;
1389 }
1390 }
1391
1392 let final_toolchain = toolchain.or_else(|| build_args.toolchain_option.clone());
1394
1395 let mut args = Args::from_build_args(build_args, command, final_toolchain)?;
1396
1397 validate_versions(&args)?;
1399
1400 if args.targets.is_empty() {
1402 let host = config::HostPlatform::detect();
1403 args.targets.push(host.triple);
1404 args.no_toolchain_setup = true;
1405 args.no_cargo_target = true;
1406 }
1407 Ok(args)
1410}
1411
1412fn parse_env_args(env_name: &str) -> Option<Vec<String>> {
1415 let value = std::env::var(env_name).ok()?;
1416 let value = value.trim();
1417 if value.is_empty() {
1418 return None;
1419 }
1420 match shlex::split(value) {
1421 Some(parsed) if !parsed.is_empty() => Some(parsed),
1422 Some(_) => None,
1423 None => {
1424 eprintln!("Warning: Failed to parse {env_name} (mismatched quotes?): {value}");
1425 None
1426 }
1427 }
1428}
1429
1430fn validate_versions(args: &Args) -> Result<()> {
1432 if !args.glibc_version.is_empty()
1435 && !SUPPORTED_GLIBC_VERSIONS.contains(&args.glibc_version.as_str())
1436 {
1437 return Err(CrossError::UnsupportedGlibcVersion {
1438 version: args.glibc_version.clone(),
1439 supported: SUPPORTED_GLIBC_VERSIONS.join(", "),
1440 });
1441 }
1442
1443 let host = config::HostPlatform::detect();
1444 if !host.is_darwin()
1445 && !SUPPORTED_IPHONE_SDK_VERSIONS.contains(&args.iphone_sdk_version.as_str())
1446 {
1447 return Err(CrossError::UnsupportedIphoneSdkVersion {
1448 version: args.iphone_sdk_version.clone(),
1449 supported: SUPPORTED_IPHONE_SDK_VERSIONS.join(", "),
1450 });
1451 }
1452
1453 if !host.is_darwin() && !SUPPORTED_MACOS_SDK_VERSIONS.contains(&args.macos_sdk_version.as_str())
1454 {
1455 return Err(CrossError::UnsupportedMacosSdkVersion {
1456 version: args.macos_sdk_version.clone(),
1457 supported: SUPPORTED_MACOS_SDK_VERSIONS.join(", "),
1458 });
1459 }
1460
1461 if !SUPPORTED_FREEBSD_VERSIONS.contains(&args.freebsd_version.as_str()) {
1462 return Err(CrossError::UnsupportedFreebsdVersion {
1463 version: args.freebsd_version.clone(),
1464 supported: SUPPORTED_FREEBSD_VERSIONS.join(", "),
1465 });
1466 }
1467
1468 Ok(())
1469}
1470
1471pub fn print_all_targets(format: OutputFormat) {
1473 let mut targets: Vec<_> = config::all_targets().collect();
1474 targets.sort_unstable();
1475
1476 match format {
1477 OutputFormat::Text => {
1478 use colored::Colorize;
1479 println!("{}", "Supported Rust targets:".bright_green());
1480 for target in &targets {
1481 println!(" {}", target.bright_cyan());
1482 }
1483 }
1484 OutputFormat::Json => {
1485 let json_array = serde_json::to_string(&targets).unwrap_or_else(|_| "[]".to_string());
1486 println!("{json_array}");
1487 }
1488 OutputFormat::Plain => {
1489 for target in &targets {
1490 println!("{target}");
1491 }
1492 }
1493 }
1494
1495 if let Ok(github_output) = std::env::var("GITHUB_OUTPUT") {
1497 let json_array = serde_json::to_string(&targets).unwrap_or_else(|_| "[]".to_string());
1498 if let Ok(mut file) = std::fs::OpenOptions::new()
1499 .append(true)
1500 .open(&github_output)
1501 {
1502 use std::io::Write;
1503 let _ = writeln!(file, "all-targets={json_array}");
1504 }
1505 }
1506}
1507
1508pub fn print_version() {
1510 use colored::Colorize;
1511
1512 let version = env!("CARGO_PKG_VERSION");
1513 let name = env!("CARGO_PKG_NAME");
1514 println!("{} {}", name.bright_green(), version.bright_cyan());
1515}
1516
1517#[cfg(test)]
1518mod tests {
1519 use super::*;
1520
1521 fn parse(args: &[&str]) -> Result<Args> {
1522 let args: Vec<String> = args.iter().map(std::string::ToString::to_string).collect();
1523 match parse_args_from(args)? {
1524 ParseResult::Build(args) => Ok(*args),
1525 ParseResult::ShowTargets(_) => panic!("unexpected ShowTargets"),
1526 ParseResult::ShowVersion => panic!("unexpected ShowVersion"),
1527 }
1528 }
1529
1530 #[test]
1534 fn test_parse_build_command() {
1535 let args = parse(&["cargo-cross", "build"]).unwrap();
1536 assert_eq!(args.command, Command::Build);
1537 }
1538
1539 #[test]
1540 fn test_parse_check_command() {
1541 let args = parse(&["cargo-cross", "check"]).unwrap();
1542 assert_eq!(args.command, Command::Check);
1543 }
1544
1545 #[test]
1546 fn test_parse_target() {
1547 let args = parse(&["cargo-cross", "build", "-t", "x86_64-unknown-linux-musl"]).unwrap();
1548 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
1549 }
1550
1551 #[test]
1552 fn test_parse_multiple_targets() {
1553 let args = parse(&[
1554 "cargo-cross",
1555 "build",
1556 "-t",
1557 "x86_64-unknown-linux-musl,aarch64-unknown-linux-musl",
1558 ])
1559 .unwrap();
1560 assert_eq!(
1561 args.targets,
1562 vec!["x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl"]
1563 );
1564 }
1565
1566 #[test]
1567 fn test_parse_verbose() {
1568 let args = parse(&["cargo-cross", "build", "-vvv"]).unwrap();
1569 assert_eq!(args.verbose_level, 3);
1570 }
1571
1572 #[test]
1573 fn test_parse_crt_static_flag() {
1574 let args = parse(&["cargo-cross", "build", "--crt-static", "true"]).unwrap();
1575 assert_eq!(args.crt_static, Some(true));
1576 }
1577
1578 #[test]
1579 fn test_parse_crt_static_false() {
1580 let args = parse(&["cargo-cross", "build", "--crt-static", "false"]).unwrap();
1581 assert_eq!(args.crt_static, Some(false));
1582 }
1583
1584 #[test]
1585 fn test_parse_crt_static_no_value() {
1586 let args = parse(&["cargo-cross", "build", "--crt-static"]).unwrap();
1588 assert_eq!(args.crt_static, Some(true));
1589 }
1590
1591 #[test]
1592 fn test_parse_crt_static_not_provided() {
1593 let args = parse(&["cargo-cross", "build"]).unwrap();
1595 assert_eq!(args.crt_static, None);
1596 }
1597
1598 #[test]
1599 fn test_parse_build_std() {
1600 let args = parse(&["cargo-cross", "build", "--build-std", "true"]).unwrap();
1601 assert_eq!(args.build_std, Some("true".to_string()));
1602 }
1603
1604 #[test]
1605 fn test_parse_build_std_crates() {
1606 let args = parse(&["cargo-cross", "build", "--build-std", "core,alloc"]).unwrap();
1607 assert_eq!(args.build_std, Some("core,alloc".to_string()));
1608 }
1609
1610 #[test]
1611 fn test_parse_build_std_false() {
1612 let args = parse(&["cargo-cross", "build", "--build-std", "false"]).unwrap();
1613 assert_eq!(args.build_std, None);
1614 }
1615
1616 #[test]
1617 fn test_parse_build_std_no_value() {
1618 let args = parse(&["cargo-cross", "build", "--build-std"]).unwrap();
1620 assert_eq!(args.build_std, Some("true".to_string()));
1621 }
1622
1623 #[test]
1624 fn test_parse_features() {
1625 let args = parse(&["cargo-cross", "build", "--features", "foo,bar"]).unwrap();
1626 assert_eq!(args.features, Some("foo,bar".to_string()));
1627 }
1628
1629 #[test]
1630 fn test_parse_no_default_features() {
1631 let args = parse(&["cargo-cross", "build", "--no-default-features"]).unwrap();
1632 assert!(args.no_default_features);
1633 }
1634
1635 #[test]
1636 fn test_parse_profile() {
1637 let args = parse(&["cargo-cross", "build", "--profile", "dev"]).unwrap();
1638 assert_eq!(args.profile, "dev");
1639 }
1640
1641 #[test]
1642 fn test_parse_jobs() {
1643 let args = parse(&["cargo-cross", "build", "-j", "4"]).unwrap();
1644 assert_eq!(args.jobs, Some("4".to_string()));
1645 }
1646
1647 #[test]
1648 fn test_parse_passthrough_args() {
1649 let args = parse(&["cargo-cross", "build", "--", "--foo", "--bar"]).unwrap();
1650 assert_eq!(args.passthrough_args, vec!["--foo", "--bar"]);
1651 }
1652
1653 #[test]
1654 fn test_parse_z_flag() {
1655 let args = parse(&["cargo-cross", "build", "-Z", "build-std"]).unwrap();
1656 assert_eq!(args.cargo_z_flags, vec!["build-std"]);
1657 }
1658
1659 #[test]
1660 fn test_parse_config_flag() {
1661 let args = parse(&["cargo-cross", "build", "--config", "opt-level=3"]).unwrap();
1662 assert_eq!(args.cargo_config, vec!["opt-level=3"]);
1663 }
1664
1665 #[test]
1666 fn test_targets_subcommand() {
1667 let args: Vec<String> = vec!["cargo-cross".to_string(), "targets".to_string()];
1668 match parse_args_from(args).unwrap() {
1669 ParseResult::ShowTargets(format) => {
1670 assert_eq!(format, OutputFormat::Text);
1671 }
1672 _ => panic!("expected ShowTargets"),
1673 }
1674 }
1675
1676 #[test]
1677 fn test_targets_json_format() {
1678 let args: Vec<String> = vec![
1679 "cargo-cross".to_string(),
1680 "targets".to_string(),
1681 "--format".to_string(),
1682 "json".to_string(),
1683 ];
1684 match parse_args_from(args).unwrap() {
1685 ParseResult::ShowTargets(format) => {
1686 assert_eq!(format, OutputFormat::Json);
1687 }
1688 _ => panic!("expected ShowTargets"),
1689 }
1690 }
1691
1692 #[test]
1693 fn test_targets_plain_format() {
1694 let args: Vec<String> = vec![
1695 "cargo-cross".to_string(),
1696 "targets".to_string(),
1697 "-f".to_string(),
1698 "plain".to_string(),
1699 ];
1700 match parse_args_from(args).unwrap() {
1701 ParseResult::ShowTargets(format) => {
1702 assert_eq!(format, OutputFormat::Plain);
1703 }
1704 _ => panic!("expected ShowTargets"),
1705 }
1706 }
1707
1708 #[test]
1709 fn test_parse_toolchain() {
1710 let args = parse(&["cargo-cross", "+nightly", "build"]).unwrap();
1711 assert_eq!(args.toolchain, Some("nightly".to_string()));
1712 assert_eq!(args.command, Command::Build);
1713 }
1714
1715 #[test]
1716 fn test_parse_toolchain_with_target() {
1717 let args = parse(&[
1718 "cargo-cross",
1719 "+nightly",
1720 "build",
1721 "-t",
1722 "x86_64-unknown-linux-musl",
1723 ])
1724 .unwrap();
1725 assert_eq!(args.toolchain, Some("nightly".to_string()));
1726 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
1727 }
1728
1729 #[test]
1732 fn test_equals_syntax_target() {
1733 let args = parse(&["cargo-cross", "build", "-t=x86_64-unknown-linux-musl"]).unwrap();
1734 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
1735 }
1736
1737 #[test]
1738 fn test_equals_syntax_long_target() {
1739 let args = parse(&["cargo-cross", "build", "--target=x86_64-unknown-linux-musl"]).unwrap();
1740 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
1741 }
1742
1743 #[test]
1744 fn test_equals_syntax_profile() {
1745 let args = parse(&["cargo-cross", "build", "--profile=dev"]).unwrap();
1746 assert_eq!(args.profile, "dev");
1747 }
1748
1749 #[test]
1750 fn test_equals_syntax_features() {
1751 let args = parse(&["cargo-cross", "build", "--features=foo,bar"]).unwrap();
1752 assert_eq!(args.features, Some("foo,bar".to_string()));
1753 }
1754
1755 #[test]
1756 fn test_equals_syntax_short_features() {
1757 let args = parse(&["cargo-cross", "build", "-F=foo,bar"]).unwrap();
1758 assert_eq!(args.features, Some("foo,bar".to_string()));
1759 }
1760
1761 #[test]
1762 fn test_equals_syntax_jobs() {
1763 let args = parse(&["cargo-cross", "build", "-j=8"]).unwrap();
1764 assert_eq!(args.jobs, Some("8".to_string()));
1765 }
1766
1767 #[test]
1768 fn test_equals_syntax_crt_static() {
1769 let args = parse(&["cargo-cross", "build", "--crt-static=true"]).unwrap();
1770 assert_eq!(args.crt_static, Some(true));
1771 }
1772
1773 #[test]
1774 fn test_equals_syntax_build_std() {
1775 let args = parse(&["cargo-cross", "build", "--build-std=core,alloc"]).unwrap();
1776 assert_eq!(args.build_std, Some("core,alloc".to_string()));
1777 }
1778
1779 #[test]
1780 fn test_equals_syntax_manifest_path() {
1781 let args = parse(&[
1782 "cargo-cross",
1783 "build",
1784 "--manifest-path=/path/to/Cargo.toml",
1785 ])
1786 .unwrap();
1787 assert_eq!(
1788 args.manifest_path,
1789 Some(PathBuf::from("/path/to/Cargo.toml"))
1790 );
1791 }
1792
1793 #[test]
1794 fn test_equals_syntax_cross_compiler_dir() {
1795 let args = parse(&["cargo-cross", "build", "--cross-compiler-dir=/opt/cross"]).unwrap();
1796 assert_eq!(args.cross_compiler_dir, PathBuf::from("/opt/cross"));
1797 }
1798
1799 #[test]
1800 fn test_equals_syntax_glibc_version() {
1801 let args = parse(&["cargo-cross", "build", "--glibc-version=2.31"]).unwrap();
1802 assert_eq!(args.glibc_version, "2.31");
1803 }
1804
1805 #[test]
1806 fn test_equals_syntax_cc_cxx_ar() {
1807 let args = parse(&[
1808 "cargo-cross",
1809 "build",
1810 "--cc=/usr/bin/gcc",
1811 "--cxx=/usr/bin/g++",
1812 "--ar=/usr/bin/ar",
1813 ])
1814 .unwrap();
1815 assert_eq!(args.cc, Some(PathBuf::from("/usr/bin/gcc")));
1816 assert_eq!(args.cxx, Some(PathBuf::from("/usr/bin/g++")));
1817 assert_eq!(args.ar, Some(PathBuf::from("/usr/bin/ar")));
1818 }
1819
1820 #[test]
1821 fn test_equals_syntax_linker() {
1822 let args = parse(&["cargo-cross", "build", "--linker=/usr/bin/ld.lld"]).unwrap();
1823 assert_eq!(args.linker, Some(PathBuf::from("/usr/bin/ld.lld")));
1824 }
1825
1826 #[test]
1827 fn test_equals_syntax_cflags_with_spaces() {
1828 let args = parse(&["cargo-cross", "build", "--cflags=-O2 -Wall -Wextra"]).unwrap();
1829 assert_eq!(args.cflags, Some("-O2 -Wall -Wextra".to_string()));
1830 }
1831
1832 #[test]
1833 fn test_equals_syntax_ldflags() {
1834 let args = parse(&["cargo-cross", "build", "--ldflags=-L/usr/local/lib -static"]).unwrap();
1835 assert_eq!(args.ldflags, Some("-L/usr/local/lib -static".to_string()));
1836 }
1837
1838 #[test]
1839 fn test_equals_syntax_rustflag() {
1840 let args = parse(&["cargo-cross", "build", "--rustflag=-C opt-level=3"]).unwrap();
1841 assert_eq!(args.rustflags, vec!["-C opt-level=3"]);
1842 }
1843
1844 #[test]
1845 fn test_equals_syntax_github_proxy() {
1846 let args = parse(&[
1847 "cargo-cross",
1848 "build",
1849 "--github-proxy=https://mirror.example.com/",
1850 ])
1851 .unwrap();
1852 assert_eq!(
1853 args.github_proxy,
1854 Some("https://mirror.example.com/".to_string())
1855 );
1856 }
1857
1858 #[test]
1859 fn test_equals_syntax_sccache_options() {
1860 let args = parse(&[
1861 "cargo-cross",
1862 "build",
1863 "--enable-sccache",
1864 "--sccache-dir=/tmp/sccache",
1865 "--sccache-cache-size=20G",
1866 ])
1867 .unwrap();
1868 assert!(args.enable_sccache);
1869 assert_eq!(args.sccache_dir, Some(PathBuf::from("/tmp/sccache")));
1870 assert_eq!(args.sccache_cache_size, Some("20G".to_string()));
1871 }
1872
1873 #[test]
1874 fn test_equals_syntax_config_with_equals_in_value() {
1875 let args = parse(&["cargo-cross", "build", "--config=build.jobs=4"]).unwrap();
1876 assert_eq!(args.cargo_config, vec!["build.jobs=4"]);
1877 }
1878
1879 #[test]
1880 fn test_equals_syntax_multiple_options() {
1881 let args = parse(&[
1882 "cargo-cross",
1883 "build",
1884 "-t=x86_64-unknown-linux-musl",
1885 "--profile=release",
1886 "-F=serde,json",
1887 "-j=8",
1888 "--crt-static=true",
1889 "--build-std=core,alloc",
1890 ])
1891 .unwrap();
1892 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
1893 assert_eq!(args.profile, "release");
1894 assert_eq!(args.features, Some("serde,json".to_string()));
1895 assert_eq!(args.jobs, Some("8".to_string()));
1896 assert_eq!(args.crt_static, Some(true));
1897 assert_eq!(args.build_std, Some("core,alloc".to_string()));
1898 }
1899
1900 #[test]
1901 fn test_equals_syntax_toolchain() {
1902 let args = parse(&["cargo-cross", "build", "--toolchain=nightly-2024-01-01"]).unwrap();
1903 assert_eq!(args.toolchain, Some("nightly-2024-01-01".to_string()));
1904 }
1905
1906 #[test]
1907 fn test_equals_syntax_target_dir() {
1908 let args = parse(&["cargo-cross", "build", "--target-dir=/tmp/target"]).unwrap();
1909 assert_eq!(args.cargo_target_dir, Some(PathBuf::from("/tmp/target")));
1910 }
1911
1912 #[test]
1913 fn test_equals_syntax_z_flag() {
1914 let args = parse(&["cargo-cross", "build", "-Z=build-std"]).unwrap();
1915 assert_eq!(args.cargo_z_flags, vec!["build-std"]);
1916 }
1917
1918 #[test]
1919 fn test_equals_syntax_directory() {
1920 let args = parse(&["cargo-cross", "build", "-C=/path/to/project"]).unwrap();
1921 assert_eq!(args.cargo_cwd, Some(PathBuf::from("/path/to/project")));
1922 }
1923
1924 #[test]
1925 fn test_equals_syntax_message_format() {
1926 let args = parse(&["cargo-cross", "build", "--message-format=json"]).unwrap();
1927 assert_eq!(args.message_format, Some("json".to_string()));
1928 }
1929
1930 #[test]
1931 fn test_equals_syntax_color() {
1932 let args = parse(&["cargo-cross", "build", "--color=always"]).unwrap();
1933 assert_eq!(args.color, Some("always".to_string()));
1934 }
1935
1936 #[test]
1939 fn test_mixed_crt_static_then_flag() {
1940 let args = parse(&[
1941 "cargo-cross",
1942 "build",
1943 "--crt-static",
1944 "true",
1945 "--no-default-features",
1946 ])
1947 .unwrap();
1948 assert_eq!(args.crt_static, Some(true));
1949 assert!(args.no_default_features);
1950 }
1951
1952 #[test]
1953 fn test_mixed_crt_static_then_short_option() {
1954 let args = parse(&[
1955 "cargo-cross",
1956 "build",
1957 "--crt-static",
1958 "false",
1959 "-F",
1960 "serde",
1961 ])
1962 .unwrap();
1963 assert_eq!(args.crt_static, Some(false));
1964 assert_eq!(args.features, Some("serde".to_string()));
1965 }
1966
1967 #[test]
1968 fn test_mixed_crt_static_with_target() {
1969 let args = parse(&[
1970 "cargo-cross",
1971 "build",
1972 "--crt-static",
1973 "true",
1974 "-t",
1975 "x86_64-unknown-linux-musl",
1976 "--profile",
1977 "release",
1978 ])
1979 .unwrap();
1980 assert_eq!(args.crt_static, Some(true));
1981 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
1982 assert_eq!(args.profile, "release");
1983 }
1984
1985 #[test]
1986 fn test_mixed_flag_then_crt_static() {
1987 let args = parse(&[
1988 "cargo-cross",
1989 "build",
1990 "--no-default-features",
1991 "--crt-static",
1992 "true",
1993 ])
1994 .unwrap();
1995 assert!(args.no_default_features);
1996 assert_eq!(args.crt_static, Some(true));
1997 }
1998
1999 #[test]
2000 fn test_mixed_multiple_flags_and_options() {
2001 let args = parse(&[
2002 "cargo-cross",
2003 "build",
2004 "-t",
2005 "aarch64-unknown-linux-musl",
2006 "--no-default-features",
2007 "-F",
2008 "serde,json",
2009 "--crt-static",
2010 "true",
2011 "--profile",
2012 "release",
2013 "-vv",
2014 ])
2015 .unwrap();
2016 assert_eq!(args.targets, vec!["aarch64-unknown-linux-musl"]);
2017 assert!(args.no_default_features);
2018 assert_eq!(args.features, Some("serde,json".to_string()));
2019 assert_eq!(args.crt_static, Some(true));
2020 assert_eq!(args.profile, "release");
2021 assert_eq!(args.verbose_level, 2);
2022 }
2023
2024 #[test]
2027 fn test_options_before_command_style() {
2028 let args = parse(&[
2030 "cargo-cross",
2031 "build",
2032 "--profile",
2033 "dev",
2034 "-t",
2035 "x86_64-unknown-linux-musl",
2036 "--features",
2037 "foo",
2038 ])
2039 .unwrap();
2040 assert_eq!(args.profile, "dev");
2041 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2042 assert_eq!(args.features, Some("foo".to_string()));
2043 }
2044
2045 #[test]
2046 fn test_interleaved_short_and_long_options() {
2047 let args = parse(&[
2048 "cargo-cross",
2049 "build",
2050 "-t",
2051 "x86_64-unknown-linux-musl",
2052 "--profile",
2053 "release",
2054 "-F",
2055 "foo",
2056 "--no-default-features",
2057 "-j",
2058 "4",
2059 "--locked",
2060 ])
2061 .unwrap();
2062 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2063 assert_eq!(args.profile, "release");
2064 assert_eq!(args.features, Some("foo".to_string()));
2065 assert!(args.no_default_features);
2066 assert_eq!(args.jobs, Some("4".to_string()));
2067 assert!(args.locked);
2068 }
2069
2070 #[test]
2073 fn test_verbose_single() {
2074 let args = parse(&["cargo-cross", "build", "-v"]).unwrap();
2075 assert_eq!(args.verbose_level, 1);
2076 }
2077
2078 #[test]
2079 fn test_verbose_double() {
2080 let args = parse(&["cargo-cross", "build", "-vv"]).unwrap();
2081 assert_eq!(args.verbose_level, 2);
2082 }
2083
2084 #[test]
2085 fn test_verbose_triple() {
2086 let args = parse(&["cargo-cross", "build", "-vvv"]).unwrap();
2087 assert_eq!(args.verbose_level, 3);
2088 }
2089
2090 #[test]
2091 fn test_verbose_separate() {
2092 let args = parse(&["cargo-cross", "build", "-v", "-v", "-v"]).unwrap();
2093 assert_eq!(args.verbose_level, 3);
2094 }
2095
2096 #[test]
2097 fn test_verbose_long_form() {
2098 let args = parse(&["cargo-cross", "build", "--verbose", "--verbose"]).unwrap();
2099 assert_eq!(args.verbose_level, 2);
2100 }
2101
2102 #[test]
2103 fn test_verbose_mixed_with_options() {
2104 let args = parse(&[
2105 "cargo-cross",
2106 "build",
2107 "-v",
2108 "-t",
2109 "x86_64-unknown-linux-musl",
2110 "-v",
2111 ])
2112 .unwrap();
2113 assert_eq!(args.verbose_level, 2);
2114 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2115 }
2116
2117 #[test]
2120 fn test_timings_without_value() {
2121 let args = parse(&["cargo-cross", "build", "--timings"]).unwrap();
2122 assert_eq!(args.timings, Some("true".to_string()));
2123 }
2124
2125 #[test]
2126 fn test_timings_with_value() {
2127 let args = parse(&["cargo-cross", "build", "--timings=html"]).unwrap();
2128 assert_eq!(args.timings, Some("html".to_string()));
2129 }
2130
2131 #[test]
2132 fn test_timings_followed_by_flag() {
2133 let args = parse(&["cargo-cross", "build", "--timings", "--locked"]).unwrap();
2134 assert_eq!(args.timings, Some("true".to_string()));
2135 assert!(args.locked);
2136 }
2137
2138 #[test]
2139 fn test_timings_followed_by_option() {
2140 let args = parse(&[
2141 "cargo-cross",
2142 "build",
2143 "--timings",
2144 "-t",
2145 "x86_64-unknown-linux-musl",
2146 ])
2147 .unwrap();
2148 assert_eq!(args.timings, Some("true".to_string()));
2149 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2150 }
2151
2152 #[test]
2155 fn test_multiple_targets_comma_separated() {
2156 let args = parse(&[
2157 "cargo-cross",
2158 "build",
2159 "-t",
2160 "x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,armv7-unknown-linux-musleabihf",
2161 ])
2162 .unwrap();
2163 assert_eq!(args.targets.len(), 3);
2164 assert_eq!(args.targets[0], "x86_64-unknown-linux-musl");
2165 assert_eq!(args.targets[1], "aarch64-unknown-linux-musl");
2166 assert_eq!(args.targets[2], "armv7-unknown-linux-musleabihf");
2167 }
2168
2169 #[test]
2170 fn test_multiple_targets_repeated_option() {
2171 let args = parse(&[
2172 "cargo-cross",
2173 "build",
2174 "-t",
2175 "x86_64-unknown-linux-musl",
2176 "-t",
2177 "aarch64-unknown-linux-musl",
2178 ])
2179 .unwrap();
2180 assert_eq!(args.targets.len(), 2);
2181 }
2182
2183 #[test]
2184 fn test_multiple_rustflags() {
2185 let args = parse(&[
2186 "cargo-cross",
2187 "build",
2188 "--rustflag",
2189 "-C opt-level=3",
2190 "--rustflag",
2191 "-C lto=thin",
2192 ])
2193 .unwrap();
2194 assert_eq!(args.rustflags.len(), 2);
2195 assert_eq!(args.rustflags[0], "-C opt-level=3");
2196 assert_eq!(args.rustflags[1], "-C lto=thin");
2197 }
2198
2199 #[test]
2200 fn test_multiple_config_flags() {
2201 let args = parse(&[
2202 "cargo-cross",
2203 "build",
2204 "--config",
2205 "build.jobs=4",
2206 "--config",
2207 "profile.release.lto=true",
2208 ])
2209 .unwrap();
2210 assert_eq!(args.cargo_config.len(), 2);
2211 }
2212
2213 #[test]
2214 fn test_multiple_z_flags() {
2215 let args = parse(&[
2216 "cargo-cross",
2217 "build",
2218 "-Z",
2219 "build-std",
2220 "-Z",
2221 "unstable-options",
2222 ])
2223 .unwrap();
2224 assert_eq!(args.cargo_z_flags.len(), 2);
2225 }
2226
2227 #[test]
2230 fn test_cflags_with_hyphen() {
2231 let args = parse(&["cargo-cross", "build", "--cflags", "-O2 -Wall"]).unwrap();
2232 assert_eq!(args.cflags, Some("-O2 -Wall".to_string()));
2233 }
2234
2235 #[test]
2236 fn test_ldflags_with_hyphen() {
2237 let args = parse(&["cargo-cross", "build", "--ldflags", "-L/usr/local/lib"]).unwrap();
2238 assert_eq!(args.ldflags, Some("-L/usr/local/lib".to_string()));
2239 }
2240
2241 #[test]
2242 fn test_rustflag_with_hyphen() {
2243 let args = parse(&["cargo-cross", "build", "--rustflag", "-C target-cpu=native"]).unwrap();
2244 assert_eq!(args.rustflags, vec!["-C target-cpu=native"]);
2245 }
2246
2247 #[test]
2250 fn test_passthrough_single() {
2251 let args = parse(&["cargo-cross", "build", "--", "--nocapture"]).unwrap();
2252 assert_eq!(args.passthrough_args, vec!["--nocapture"]);
2253 }
2254
2255 #[test]
2256 fn test_passthrough_multiple() {
2257 let args = parse(&[
2258 "cargo-cross",
2259 "test",
2260 "--",
2261 "--nocapture",
2262 "--test-threads=1",
2263 ])
2264 .unwrap();
2265 assert_eq!(
2266 args.passthrough_args,
2267 vec!["--nocapture", "--test-threads=1"]
2268 );
2269 }
2270
2271 #[test]
2272 fn test_passthrough_with_hyphen_values() {
2273 let args = parse(&["cargo-cross", "build", "--", "-v", "--foo", "-bar"]).unwrap();
2274 assert_eq!(args.passthrough_args, vec!["-v", "--foo", "-bar"]);
2275 }
2276
2277 #[test]
2278 fn test_passthrough_after_options() {
2279 let args = parse(&[
2280 "cargo-cross",
2281 "build",
2282 "-t",
2283 "x86_64-unknown-linux-musl",
2284 "--profile",
2285 "release",
2286 "--",
2287 "--foo",
2288 "--bar",
2289 ])
2290 .unwrap();
2291 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2292 assert_eq!(args.profile, "release");
2293 assert_eq!(args.passthrough_args, vec!["--foo", "--bar"]);
2294 }
2295
2296 #[test]
2299 fn test_alias_targets() {
2300 let args = parse(&[
2301 "cargo-cross",
2302 "build",
2303 "--targets",
2304 "x86_64-unknown-linux-musl",
2305 ])
2306 .unwrap();
2307 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2308 }
2309
2310 #[test]
2311 fn test_alias_workspace_all() {
2312 let args = parse(&["cargo-cross", "build", "--all"]).unwrap();
2313 assert!(args.workspace);
2314 }
2315
2316 #[test]
2317 fn test_alias_rustflags() {
2318 let args = parse(&["cargo-cross", "build", "--rustflags", "-C lto"]).unwrap();
2319 assert_eq!(args.rustflags, vec!["-C lto"]);
2320 }
2321
2322 #[test]
2323 fn test_alias_trim_paths() {
2324 let args = parse(&["cargo-cross", "build", "--trim-paths", "all"]).unwrap();
2325 assert_eq!(args.cargo_trim_paths, Some("all".to_string()));
2326 }
2327
2328 #[test]
2329 fn test_trim_paths_no_value() {
2330 let args = parse(&["cargo-cross", "build", "--trim-paths"]).unwrap();
2332 assert_eq!(args.cargo_trim_paths, Some("true".to_string()));
2333 }
2334
2335 #[test]
2336 fn test_cargo_trim_paths_no_value() {
2337 let args = parse(&["cargo-cross", "build", "--cargo-trim-paths"]).unwrap();
2339 assert_eq!(args.cargo_trim_paths, Some("true".to_string()));
2340 }
2341
2342 #[test]
2343 fn test_rustc_bootstrap_no_value() {
2344 let args = parse(&["cargo-cross", "build", "--rustc-bootstrap"]).unwrap();
2346 assert_eq!(args.rustc_bootstrap, Some("1".to_string()));
2347 }
2348
2349 #[test]
2350 fn test_rustc_bootstrap_with_value() {
2351 let args = parse(&["cargo-cross", "build", "--rustc-bootstrap", "mycrate"]).unwrap();
2352 assert_eq!(args.rustc_bootstrap, Some("mycrate".to_string()));
2353 }
2354
2355 #[test]
2358 fn test_command_alias_b() {
2359 let args = parse(&["cargo-cross", "b"]).unwrap();
2360 assert_eq!(args.command, Command::Build);
2361 }
2362
2363 #[test]
2364 fn test_command_alias_c() {
2365 let args = parse(&["cargo-cross", "c"]).unwrap();
2366 assert_eq!(args.command, Command::Check);
2367 }
2368
2369 #[test]
2370 fn test_command_alias_r() {
2371 let args = parse(&["cargo-cross", "r"]).unwrap();
2372 assert_eq!(args.command, Command::Run);
2373 }
2374
2375 #[test]
2376 fn test_command_alias_t() {
2377 let args = parse(&["cargo-cross", "t"]).unwrap();
2378 assert_eq!(args.command, Command::Test);
2379 }
2380
2381 #[test]
2384 fn test_requires_exclude_needs_workspace() {
2385 let result = parse(&["cargo-cross", "build", "--exclude", "foo"]);
2386 assert!(
2387 result.is_err() || {
2388 false
2390 }
2391 );
2392 }
2393
2394 #[test]
2395 fn test_requires_exclude_with_workspace() {
2396 let args = parse(&["cargo-cross", "build", "--workspace", "--exclude", "foo"]).unwrap();
2397 assert!(args.workspace);
2398 assert_eq!(args.exclude, Some("foo".to_string()));
2399 }
2400
2401 #[test]
2402 fn test_requires_build_std_features_with_build_std() {
2403 let args = parse(&[
2404 "cargo-cross",
2405 "build",
2406 "--build-std",
2407 "core,alloc",
2408 "--build-std-features",
2409 "panic_immediate_abort",
2410 ])
2411 .unwrap();
2412 assert_eq!(args.build_std, Some("core,alloc".to_string()));
2413 assert_eq!(
2414 args.build_std_features,
2415 Some("panic_immediate_abort".to_string())
2416 );
2417 }
2418
2419 #[test]
2422 fn test_conflicts_quiet_verbose() {
2423 let result = parse(&["cargo-cross", "build", "--quiet", "--verbose"]);
2424 assert!(
2426 result.is_err() || {
2427 false
2429 }
2430 );
2431 }
2432
2433 #[test]
2434 fn test_conflicts_features_all_features() {
2435 let result = parse(&[
2436 "cargo-cross",
2437 "build",
2438 "--features",
2439 "foo",
2440 "--all-features",
2441 ]);
2442 assert!(result.is_err());
2443 }
2444
2445 #[test]
2446 fn test_no_toolchain_setup() {
2447 let args = parse(&["cargo-cross", "build", "--no-toolchain-setup"]).unwrap();
2448 assert!(args.no_toolchain_setup);
2449 }
2450
2451 #[test]
2452 fn test_linker_with_no_toolchain_setup() {
2453 let args = parse(&[
2455 "cargo-cross",
2456 "build",
2457 "--linker",
2458 "/usr/bin/ld",
2459 "--no-toolchain-setup",
2460 ])
2461 .unwrap();
2462 assert!(args.no_toolchain_setup);
2463 assert_eq!(args.linker, Some(PathBuf::from("/usr/bin/ld")));
2464 }
2465
2466 #[test]
2469 fn test_real_world_linux_musl_build() {
2470 let args = parse(&[
2471 "cargo-cross",
2472 "+nightly",
2473 "build",
2474 "-t",
2475 "x86_64-unknown-linux-musl",
2476 "--profile",
2477 "release",
2478 "--crt-static",
2479 "true",
2480 "--no-default-features",
2481 "-F",
2482 "serde,json",
2483 "-j",
2484 "8",
2485 "--locked",
2486 ])
2487 .unwrap();
2488 assert_eq!(args.toolchain, Some("nightly".to_string()));
2489 assert_eq!(args.command, Command::Build);
2490 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2491 assert_eq!(args.profile, "release");
2492 assert_eq!(args.crt_static, Some(true));
2493 assert!(args.no_default_features);
2494 assert_eq!(args.features, Some("serde,json".to_string()));
2495 assert_eq!(args.jobs, Some("8".to_string()));
2496 assert!(args.locked);
2497 }
2498
2499 #[test]
2500 fn test_real_world_multi_target_build() {
2501 let args = parse(&[
2502 "cargo-cross",
2503 "build",
2504 "-t",
2505 "x86_64-unknown-linux-musl,aarch64-unknown-linux-musl",
2506 "--profile",
2507 "release",
2508 "--build-std",
2509 "core,alloc",
2510 "--build-std-features",
2511 "panic_immediate_abort",
2512 "-vv",
2513 ])
2514 .unwrap();
2515 assert_eq!(args.targets.len(), 2);
2516 assert_eq!(args.build_std, Some("core,alloc".to_string()));
2517 assert_eq!(
2518 args.build_std_features,
2519 Some("panic_immediate_abort".to_string())
2520 );
2521 assert_eq!(args.verbose_level, 2);
2522 }
2523
2524 #[test]
2525 fn test_real_world_test_with_passthrough() {
2526 let args = parse(&[
2527 "cargo-cross",
2528 "test",
2529 "-t",
2530 "x86_64-unknown-linux-musl",
2531 "--",
2532 "--nocapture",
2533 "--test-threads=1",
2534 ])
2535 .unwrap();
2536 assert_eq!(args.command, Command::Test);
2537 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2538 assert_eq!(
2539 args.passthrough_args,
2540 vec!["--nocapture", "--test-threads=1"]
2541 );
2542 }
2543
2544 #[test]
2545 fn test_real_world_with_compiler_options() {
2546 let args = parse(&[
2547 "cargo-cross",
2548 "build",
2549 "-t",
2550 "aarch64-unknown-linux-musl",
2551 "--cc",
2552 "/opt/cross/bin/aarch64-linux-musl-gcc",
2553 "--cxx",
2554 "/opt/cross/bin/aarch64-linux-musl-g++",
2555 "--ar",
2556 "/opt/cross/bin/aarch64-linux-musl-ar",
2557 "--cflags",
2558 "-O2 -march=armv8-a",
2559 ])
2560 .unwrap();
2561 assert_eq!(args.targets, vec!["aarch64-unknown-linux-musl"]);
2562 assert!(args.cc.is_some());
2563 assert!(args.cxx.is_some());
2564 assert!(args.ar.is_some());
2565 assert_eq!(args.cflags, Some("-O2 -march=armv8-a".to_string()));
2566 }
2567
2568 #[test]
2569 fn test_real_world_sccache_build() {
2570 let args = parse(&[
2571 "cargo-cross",
2572 "build",
2573 "-t",
2574 "x86_64-unknown-linux-musl",
2575 "--enable-sccache",
2576 "--sccache-dir",
2577 "/tmp/sccache",
2578 "--sccache-cache-size",
2579 "10G",
2580 ])
2581 .unwrap();
2582 assert!(args.enable_sccache);
2583 assert_eq!(args.sccache_dir, Some(PathBuf::from("/tmp/sccache")));
2584 assert_eq!(args.sccache_cache_size, Some("10G".to_string()));
2585 }
2586
2587 #[test]
2588 fn test_real_world_workspace_build() {
2589 let args = parse(&[
2590 "cargo-cross",
2591 "build",
2592 "--workspace",
2593 "--exclude",
2594 "test-crate",
2595 "-t",
2596 "x86_64-unknown-linux-musl",
2597 "--profile",
2598 "release",
2599 ])
2600 .unwrap();
2601 assert!(args.workspace);
2602 assert_eq!(args.exclude, Some("test-crate".to_string()));
2603 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2604 }
2605
2606 #[test]
2609 fn test_edge_case_equals_in_value() {
2610 let args = parse(&[
2611 "cargo-cross",
2612 "build",
2613 "--config",
2614 "build.rustflags=['-C', 'opt-level=3']",
2615 ])
2616 .unwrap();
2617 assert_eq!(
2618 args.cargo_config,
2619 vec!["build.rustflags=['-C', 'opt-level=3']"]
2620 );
2621 }
2622
2623 #[test]
2624 fn test_edge_case_empty_passthrough() {
2625 let args = parse(&["cargo-cross", "build", "--"]).unwrap();
2626 assert!(args.passthrough_args.is_empty());
2627 }
2628
2629 #[test]
2630 fn test_edge_case_target_with_numbers() {
2631 let args = parse(&[
2632 "cargo-cross",
2633 "build",
2634 "-t",
2635 "armv7-unknown-linux-musleabihf",
2636 ])
2637 .unwrap();
2638 assert_eq!(args.targets, vec!["armv7-unknown-linux-musleabihf"]);
2639 }
2640
2641 #[test]
2642 fn test_edge_case_all_bool_options() {
2643 let args = parse(&[
2644 "cargo-cross",
2645 "build",
2646 "--no-default-features",
2647 "--workspace",
2648 "--bins",
2649 "--lib",
2650 "--examples",
2651 "--tests",
2652 "--benches",
2653 "--all-targets",
2654 "--locked",
2655 "--offline",
2656 "--keep-going",
2657 ])
2658 .unwrap();
2659 assert!(args.no_default_features);
2660 assert!(args.workspace);
2661 assert!(args.build_bins);
2662 assert!(args.build_lib);
2663 assert!(args.build_examples);
2664 assert!(args.build_tests);
2665 assert!(args.build_benches);
2666 assert!(args.build_all_targets);
2667 assert!(args.locked);
2668 assert!(args.offline);
2669 assert!(args.keep_going);
2670 }
2671
2672 #[test]
2673 fn test_edge_case_mixed_equals_and_space() {
2674 let args = parse(&[
2675 "cargo-cross",
2676 "build",
2677 "-t=x86_64-unknown-linux-musl",
2678 "--profile",
2679 "release",
2680 "-F=serde",
2681 "--crt-static",
2682 "true",
2683 ])
2684 .unwrap();
2685 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2686 assert_eq!(args.profile, "release");
2687 assert_eq!(args.features, Some("serde".to_string()));
2688 assert_eq!(args.crt_static, Some(true));
2689 }
2690
2691 #[test]
2692 fn test_edge_case_directory_option() {
2693 let args = parse(&[
2694 "cargo-cross",
2695 "build",
2696 "-C",
2697 "/path/to/project",
2698 "-t",
2699 "x86_64-unknown-linux-musl",
2700 ])
2701 .unwrap();
2702 assert_eq!(args.cargo_cwd, Some(PathBuf::from("/path/to/project")));
2703 }
2704
2705 #[test]
2706 fn test_edge_case_manifest_path() {
2707 let args = parse(&[
2708 "cargo-cross",
2709 "build",
2710 "--manifest-path",
2711 "/path/to/Cargo.toml",
2712 ])
2713 .unwrap();
2714 assert_eq!(
2715 args.manifest_path,
2716 Some(PathBuf::from("/path/to/Cargo.toml"))
2717 );
2718 }
2719
2720 #[test]
2723 fn test_cargo_cross_style_build() {
2724 let args: Vec<String> = vec![
2725 "cargo-cross".to_string(),
2726 "cross".to_string(),
2727 "build".to_string(),
2728 "-t".to_string(),
2729 "x86_64-unknown-linux-musl".to_string(),
2730 ];
2731 match parse_args_from(args).unwrap() {
2732 ParseResult::Build(args) => {
2733 assert_eq!(args.command, Command::Build);
2734 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
2735 }
2736 _ => panic!("expected Build"),
2737 }
2738 }
2739
2740 #[test]
2741 fn test_cargo_cross_style_with_toolchain() {
2742 let args: Vec<String> = vec![
2743 "cargo-cross".to_string(),
2744 "cross".to_string(),
2745 "+nightly".to_string(),
2746 "build".to_string(),
2747 ];
2748 match parse_args_from(args).unwrap() {
2749 ParseResult::Build(args) => {
2750 assert_eq!(args.toolchain, Some("nightly".to_string()));
2751 assert_eq!(args.command, Command::Build);
2752 }
2753 _ => panic!("expected Build"),
2754 }
2755 }
2756
2757 #[test]
2758 fn test_cargo_cross_style_targets() {
2759 let args: Vec<String> = vec![
2760 "cargo-cross".to_string(),
2761 "cross".to_string(),
2762 "targets".to_string(),
2763 ];
2764 match parse_args_from(args).unwrap() {
2765 ParseResult::ShowTargets(_) => {}
2766 _ => panic!("expected ShowTargets"),
2767 }
2768 }
2769
2770 #[test]
2773 fn test_github_proxy_mirror_alias() {
2774 let args = parse(&[
2775 "cargo-cross",
2776 "build",
2777 "--github-proxy-mirror",
2778 "https://mirror.example.com/",
2779 ])
2780 .unwrap();
2781 assert_eq!(
2782 args.github_proxy,
2783 Some("https://mirror.example.com/".to_string())
2784 );
2785 }
2786
2787 #[test]
2788 fn test_github_proxy_original() {
2789 let args = parse(&[
2790 "cargo-cross",
2791 "build",
2792 "--github-proxy",
2793 "https://proxy.example.com/",
2794 ])
2795 .unwrap();
2796 assert_eq!(
2797 args.github_proxy,
2798 Some("https://proxy.example.com/".to_string())
2799 );
2800 }
2801
2802 #[test]
2803 fn test_release_flag_short() {
2804 let args = parse(&["cargo-cross", "build", "-r"]).unwrap();
2805 assert!(args.release);
2806 assert_eq!(args.profile, "release");
2807 }
2808
2809 #[test]
2810 fn test_release_flag_long() {
2811 let args = parse(&["cargo-cross", "build", "--release"]).unwrap();
2812 assert!(args.release);
2813 assert_eq!(args.profile, "release");
2814 }
2815
2816 #[test]
2817 fn test_toolchain_option() {
2818 let args = parse(&["cargo-cross", "build", "--toolchain", "nightly"]).unwrap();
2819 assert_eq!(args.toolchain, Some("nightly".to_string()));
2820 }
2821
2822 #[test]
2823 fn test_toolchain_option_with_version() {
2824 let args = parse(&["cargo-cross", "build", "--toolchain", "1.75.0"]).unwrap();
2825 assert_eq!(args.toolchain, Some("1.75.0".to_string()));
2826 }
2827
2828 #[test]
2829 fn test_toolchain_plus_syntax_takes_precedence() {
2830 let args = parse(&["cargo-cross", "+nightly", "build", "--toolchain", "stable"]).unwrap();
2831 assert_eq!(args.toolchain, Some("nightly".to_string()));
2833 }
2834
2835 #[test]
2836 fn test_target_dir_alias() {
2837 let args = parse(&["cargo-cross", "build", "--target-dir", "/tmp/target"]).unwrap();
2838 assert_eq!(args.cargo_target_dir, Some(PathBuf::from("/tmp/target")));
2839 }
2840
2841 #[test]
2842 fn test_cargo_target_dir_original() {
2843 let args = parse(&["cargo-cross", "build", "--cargo-target-dir", "/tmp/target"]).unwrap();
2844 assert_eq!(args.cargo_target_dir, Some(PathBuf::from("/tmp/target")));
2845 }
2846
2847 #[test]
2848 fn test_args_alias() {
2849 let args = parse(&["cargo-cross", "build", "--args", "--verbose"]).unwrap();
2850 assert_eq!(args.cargo_args, vec!["--verbose"]);
2851 }
2852
2853 #[test]
2854 fn test_cargo_args_original() {
2855 let args = parse(&["cargo-cross", "build", "--cargo-args", "--verbose"]).unwrap();
2856 assert_eq!(args.cargo_args, vec!["--verbose"]);
2857 }
2858
2859 #[test]
2860 fn test_cargo_args_multiple() {
2861 let args = parse(&[
2862 "cargo-cross",
2863 "build",
2864 "--cargo-args",
2865 "--verbose",
2866 "--cargo-args",
2867 "--locked",
2868 ])
2869 .unwrap();
2870 assert_eq!(args.cargo_args, vec!["--verbose", "--locked"]);
2871 }
2872
2873 #[test]
2876 fn test_invalid_target_triple_uppercase() {
2877 let result = parse(&["cargo-cross", "build", "-t", "X86_64-unknown-linux-musl"]);
2878 assert!(result.is_err());
2879 let err = result.unwrap_err();
2880 assert!(
2881 matches!(err, CrossError::InvalidTargetTriple { target, char } if target == "X86_64-unknown-linux-musl" && char == 'X')
2882 );
2883 }
2884
2885 #[test]
2886 fn test_glob_pattern_matches_target() {
2887 let args = parse(&["cargo-cross", "build", "-t", "x86_64*unknown-linux-musl"]).unwrap();
2889 assert!(args
2890 .targets
2891 .contains(&"x86_64-unknown-linux-musl".to_string()));
2892 }
2893
2894 #[test]
2895 fn test_invalid_target_triple_slash() {
2896 let result = parse(&["cargo-cross", "build", "-t", "x86_64/unknown-linux-musl"]);
2898 assert!(result.is_err());
2899 let err = result.unwrap_err();
2900 assert!(
2901 matches!(err, CrossError::InvalidTargetTriple { target, char } if target == "x86_64/unknown-linux-musl" && char == '/')
2902 );
2903 }
2904
2905 #[test]
2906 fn test_invalid_target_triple_dot() {
2907 let result = parse(&["cargo-cross", "build", "-t", "x86_64.unknown-linux-musl"]);
2908 assert!(result.is_err());
2909 let err = result.unwrap_err();
2910 assert!(
2911 matches!(err, CrossError::InvalidTargetTriple { target, char } if target == "x86_64.unknown-linux-musl" && char == '.')
2912 );
2913 }
2914
2915 #[test]
2916 fn test_no_matching_targets_glob() {
2917 let result = parse(&["cargo-cross", "build", "-t", "*mingw*"]);
2918 assert!(result.is_err());
2919 let err = result.unwrap_err();
2920 assert!(matches!(
2921 err,
2922 CrossError::NoMatchingTargets { pattern } if pattern == "*mingw*"
2923 ));
2924 }
2925
2926 #[test]
2927 fn test_no_matching_targets_glob_complex() {
2928 let result = parse(&["cargo-cross", "build", "-t", "*nonexistent-platform*"]);
2929 assert!(result.is_err());
2930 let err = result.unwrap_err();
2931 assert!(matches!(
2932 err,
2933 CrossError::NoMatchingTargets { pattern } if pattern == "*nonexistent-platform*"
2934 ));
2935 }
2936
2937 #[test]
2938 fn test_valid_target_triple_with_numbers() {
2939 let args = parse(&[
2940 "cargo-cross",
2941 "build",
2942 "-t",
2943 "armv7-unknown-linux-gnueabihf",
2944 ])
2945 .unwrap();
2946 assert_eq!(args.targets, vec!["armv7-unknown-linux-gnueabihf"]);
2947 }
2948
2949 #[test]
2950 fn test_valid_target_triple_underscore() {
2951 let args = parse(&["cargo-cross", "build", "-t", "x86_64_unknown_linux_musl"]).unwrap();
2952 assert_eq!(args.targets, vec!["x86_64_unknown_linux_musl"]);
2953 }
2954
2955 #[test]
2956 fn test_valid_glob_pattern_matches() {
2957 let args = parse(&["cargo-cross", "build", "-t", "*-linux-musl"]).unwrap();
2958 assert!(!args.targets.is_empty());
2959 for target in &args.targets {
2960 assert!(target.ends_with("-linux-musl"));
2961 }
2962 }
2963
2964 #[test]
2965 fn test_is_glob_pattern() {
2966 assert!(is_glob_pattern("*-linux-musl"));
2967 assert!(is_glob_pattern("x86_64-*-linux"));
2968 assert!(is_glob_pattern("x86_64-?-linux"));
2969 assert!(is_glob_pattern("[ab]-linux"));
2970 assert!(!is_glob_pattern("x86_64-unknown-linux-musl"));
2971 assert!(!is_glob_pattern("aarch64-linux-android"));
2972 }
2973
2974 #[test]
2975 fn test_validate_target_triple() {
2976 assert!(validate_target_triple("x86_64-unknown-linux-musl").is_ok());
2977 assert!(validate_target_triple("aarch64-unknown-linux-gnu").is_ok());
2978 assert!(validate_target_triple("armv7-unknown-linux-gnueabihf").is_ok());
2979 assert!(validate_target_triple("i686-pc-windows-msvc").is_ok());
2980 assert!(validate_target_triple("x86_64-pc-windows-msvc").is_ok());
2981 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()); }
2986
2987 #[test]
2990 fn test_short_concat_features() {
2991 let args = parse(&["cargo-cross", "build", "-Ffoo,bar"]).unwrap();
2992 assert_eq!(args.features, Some("foo,bar".to_string()));
2993 }
2994
2995 #[test]
2996 fn test_short_concat_jobs() {
2997 let args = parse(&["cargo-cross", "build", "-j4"]).unwrap();
2998 assert_eq!(args.jobs, Some("4".to_string()));
2999 }
3000
3001 #[test]
3002 fn test_short_concat_package() {
3003 let args = parse(&["cargo-cross", "build", "-pmypackage"]).unwrap();
3004 assert_eq!(args.package, Some("mypackage".to_string()));
3005 }
3006
3007 #[test]
3008 fn test_short_concat_target() {
3009 let args = parse(&["cargo-cross", "build", "-tx86_64-unknown-linux-musl"]).unwrap();
3010 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3011 }
3012
3013 #[test]
3014 fn test_short_concat_z_flag() {
3015 let args = parse(&["cargo-cross", "build", "-Zbuild-std"]).unwrap();
3016 assert_eq!(args.cargo_z_flags, vec!["build-std"]);
3017 }
3018
3019 #[test]
3020 fn test_short_concat_directory() {
3021 let args = parse(&["cargo-cross", "build", "-C/path/to/project"]).unwrap();
3022 assert_eq!(args.cargo_cwd, Some(PathBuf::from("/path/to/project")));
3023 }
3024
3025 #[test]
3026 fn test_short_concat_multiple() {
3027 let args = parse(&[
3028 "cargo-cross",
3029 "build",
3030 "-tx86_64-unknown-linux-musl",
3031 "-Ffoo,bar",
3032 "-j8",
3033 "-pmypkg",
3034 ])
3035 .unwrap();
3036 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3037 assert_eq!(args.features, Some("foo,bar".to_string()));
3038 assert_eq!(args.jobs, Some("8".to_string()));
3039 assert_eq!(args.package, Some("mypkg".to_string()));
3040 }
3041
3042 #[test]
3043 fn test_short_concat_mixed_with_space() {
3044 let args = parse(&[
3045 "cargo-cross",
3046 "build",
3047 "-j4",
3048 "-t",
3049 "x86_64-unknown-linux-musl",
3050 "-Fbar",
3051 ])
3052 .unwrap();
3053 assert_eq!(args.jobs, Some("4".to_string()));
3054 assert_eq!(args.targets, vec!["x86_64-unknown-linux-musl"]);
3055 assert_eq!(args.features, Some("bar".to_string()));
3056 }
3057
3058 #[test]
3061 fn test_parse_env_args_not_set() {
3062 std::env::remove_var("TEST_PARSE_ENV_ARGS_NOT_SET");
3063 let result = parse_env_args("TEST_PARSE_ENV_ARGS_NOT_SET");
3064 assert!(result.is_none());
3065 }
3066
3067 #[test]
3068 fn test_parse_env_args_empty() {
3069 std::env::set_var("TEST_PARSE_ENV_ARGS_EMPTY", "");
3070 let result = parse_env_args("TEST_PARSE_ENV_ARGS_EMPTY");
3071 assert!(result.is_none());
3072 std::env::remove_var("TEST_PARSE_ENV_ARGS_EMPTY");
3073 }
3074
3075 #[test]
3076 fn test_parse_env_args_simple() {
3077 std::env::set_var("TEST_PARSE_ENV_ARGS_SIMPLE", "--verbose --locked");
3078 let result = parse_env_args("TEST_PARSE_ENV_ARGS_SIMPLE");
3079 assert_eq!(
3080 result,
3081 Some(vec!["--verbose".to_string(), "--locked".to_string()])
3082 );
3083 std::env::remove_var("TEST_PARSE_ENV_ARGS_SIMPLE");
3084 }
3085
3086 #[test]
3087 fn test_parse_env_args_with_single_quotes() {
3088 std::env::set_var(
3089 "TEST_PARSE_ENV_ARGS_SINGLE_QUOTES",
3090 "--config 'build.jobs=4' --verbose",
3091 );
3092 let result = parse_env_args("TEST_PARSE_ENV_ARGS_SINGLE_QUOTES");
3093 assert_eq!(
3094 result,
3095 Some(vec![
3096 "--config".to_string(),
3097 "build.jobs=4".to_string(),
3098 "--verbose".to_string()
3099 ])
3100 );
3101 std::env::remove_var("TEST_PARSE_ENV_ARGS_SINGLE_QUOTES");
3102 }
3103
3104 #[test]
3105 fn test_parse_env_args_with_double_quotes() {
3106 std::env::set_var(
3107 "TEST_PARSE_ENV_ARGS_DOUBLE_QUOTES",
3108 "--message-format \"json with spaces\"",
3109 );
3110 let result = parse_env_args("TEST_PARSE_ENV_ARGS_DOUBLE_QUOTES");
3111 assert_eq!(
3112 result,
3113 Some(vec![
3114 "--message-format".to_string(),
3115 "json with spaces".to_string()
3116 ])
3117 );
3118 std::env::remove_var("TEST_PARSE_ENV_ARGS_DOUBLE_QUOTES");
3119 }
3120
3121 #[test]
3122 fn test_parse_env_args_complex() {
3123 std::env::set_var(
3124 "TEST_PARSE_ENV_ARGS_COMPLEX",
3125 "--config 'key=\"value with spaces\"' --verbose",
3126 );
3127 let result = parse_env_args("TEST_PARSE_ENV_ARGS_COMPLEX");
3128 assert_eq!(
3129 result,
3130 Some(vec![
3131 "--config".to_string(),
3132 "key=\"value with spaces\"".to_string(),
3133 "--verbose".to_string()
3134 ])
3135 );
3136 std::env::remove_var("TEST_PARSE_ENV_ARGS_COMPLEX");
3137 }
3138
3139 #[test]
3140 fn test_parse_env_args_whitespace_only() {
3141 std::env::set_var("TEST_PARSE_ENV_ARGS_WHITESPACE", " ");
3142 let result = parse_env_args("TEST_PARSE_ENV_ARGS_WHITESPACE");
3143 assert!(result.is_none());
3144 std::env::remove_var("TEST_PARSE_ENV_ARGS_WHITESPACE");
3145 }
3146
3147 #[test]
3148 fn test_musl_target_with_default_glibc() {
3149 let args = parse(&[
3151 "cargo-cross",
3152 "build",
3153 "-t",
3154 "aarch64_be-unknown-linux-musl",
3155 ])
3156 .unwrap();
3157 assert_eq!(args.targets, vec!["aarch64_be-unknown-linux-musl"]);
3158 assert_eq!(args.glibc_version, ""); }
3160}