1use super::ExtractedCustomPlatform;
5use crate::{
6 cargo_config::{CargoConfigSource, CargoConfigs, DiscoveredConfig},
7 errors::TargetTripleError,
8};
9use camino::{Utf8Path, Utf8PathBuf};
10use std::fmt;
11use target_spec::{Platform, TargetFeatures, summaries::PlatformSummary};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct TargetTriple {
16 pub platform: Platform,
18
19 pub source: TargetTripleSource,
21
22 pub location: TargetDefinitionLocation,
24}
25
26impl TargetTriple {
27 pub fn deserialize(
30 platform: Option<PlatformSummary>,
31 ) -> Result<Option<TargetTriple>, target_spec::Error> {
32 platform
33 .map(|summary| {
34 let platform = summary.to_platform()?;
35 let location = if platform.is_custom() {
36 TargetDefinitionLocation::MetadataCustom(
37 summary
38 .custom_json
39 .expect("custom platform <=> custom JSON"),
40 )
41 } else {
42 TargetDefinitionLocation::Builtin
43 };
44 Ok(TargetTriple {
45 platform,
46 source: TargetTripleSource::Metadata,
47 location,
48 })
49 })
50 .transpose()
51 }
52
53 pub fn deserialize_str(
55 triple_str: Option<String>,
56 ) -> Result<Option<TargetTriple>, target_spec::Error> {
57 triple_str
58 .map(|triple_str| {
59 Ok(TargetTriple {
60 platform: Platform::new(triple_str, TargetFeatures::Unknown)?,
61 source: TargetTripleSource::Metadata,
62 location: TargetDefinitionLocation::Builtin,
63 })
64 })
65 .transpose()
66 }
67
68 pub fn to_cargo_target_arg(&self) -> Result<CargoTargetArg, TargetTripleError> {
74 match &self.location {
75 TargetDefinitionLocation::Builtin | TargetDefinitionLocation::Heuristic => Ok(
77 CargoTargetArg::Builtin(self.platform.triple_str().to_string()),
78 ),
79 TargetDefinitionLocation::DirectPath(path)
80 | TargetDefinitionLocation::RustTargetPath(path) => {
81 Ok(CargoTargetArg::Path(path.clone()))
82 }
83 TargetDefinitionLocation::MetadataCustom(json) => CargoTargetArg::from_custom_json(
84 self.platform.triple_str(),
85 json,
86 self.source.clone(),
87 ),
88 }
89 }
90
91 pub fn find(
102 cargo_configs: &CargoConfigs,
103 target_cli_option: Option<&str>,
104 host_platform: &Platform,
105 ) -> Result<Option<Self>, TargetTripleError> {
106 if let Some(triple_str_or_path) = target_cli_option {
108 let ret = Self::resolve_triple(
109 triple_str_or_path,
110 TargetTripleSource::CliOption,
111 cargo_configs.cwd(),
112 cargo_configs.target_paths(),
113 host_platform,
114 )?;
115 return Ok(Some(ret));
116 }
117
118 Self::from_cargo_configs(cargo_configs, host_platform)
120 }
121
122 pub const CARGO_BUILD_TARGET_ENV: &'static str = "CARGO_BUILD_TARGET";
124
125 fn from_env(
126 cwd: &Utf8Path,
127 target_paths: &[Utf8PathBuf],
128 host_platform: &Platform,
129 ) -> Result<Option<Self>, TargetTripleError> {
130 if let Some(triple_val) = std::env::var_os(Self::CARGO_BUILD_TARGET_ENV) {
131 let triple = triple_val
132 .into_string()
133 .map_err(|_osstr| TargetTripleError::InvalidEnvironmentVar)?;
134 let ret = Self::resolve_triple(
135 &triple,
136 TargetTripleSource::Env,
137 cwd,
138 target_paths,
139 host_platform,
140 )?;
141 Ok(Some(ret))
142 } else {
143 Ok(None)
144 }
145 }
146
147 fn from_cargo_configs(
148 cargo_configs: &CargoConfigs,
149 host_platform: &Platform,
150 ) -> Result<Option<Self>, TargetTripleError> {
151 for discovered_config in cargo_configs.discovered_configs() {
152 match discovered_config {
153 DiscoveredConfig::CliOption { config, source }
154 | DiscoveredConfig::File { config, source } => {
155 if let Some(triple) = &config.build.target {
156 let resolve_dir = source.resolve_dir(cargo_configs.cwd());
157 let source = TargetTripleSource::CargoConfig {
158 source: source.clone(),
159 };
160 let ret = Self::resolve_triple(
161 triple,
162 source,
163 resolve_dir,
164 cargo_configs.target_paths(),
165 host_platform,
166 )?;
167 return Ok(Some(ret));
168 }
169 }
170 DiscoveredConfig::Env => {
171 if let Some(triple) = Self::from_env(
173 cargo_configs.cwd(),
174 cargo_configs.target_paths(),
175 host_platform,
176 )? {
177 return Ok(Some(triple));
178 }
179 }
180 }
181 }
182
183 Ok(None)
184 }
185
186 fn resolve_triple(
190 triple_str_or_path: &str,
191 source: TargetTripleSource,
192 resolve_dir: &Utf8Path,
195 target_paths: &[Utf8PathBuf],
196 host_platform: &Platform,
197 ) -> Result<Self, TargetTripleError> {
198 if triple_str_or_path == "host-tuple" {
200 return Ok(Self {
201 platform: host_platform.clone(),
202 source,
203 location: TargetDefinitionLocation::Builtin,
204 });
205 }
206
207 if triple_str_or_path.ends_with(".json") {
208 return Self::custom_from_path(triple_str_or_path.as_ref(), source, resolve_dir);
209 }
210
211 if let Ok(platform) =
213 Platform::new_strict(triple_str_or_path.to_owned(), TargetFeatures::Unknown)
214 {
215 return Ok(Self {
216 platform,
217 source,
218 location: TargetDefinitionLocation::Builtin,
219 });
220 }
221
222 let triple_filename = {
224 let mut triple_str = triple_str_or_path.to_owned();
225 triple_str.push_str(".json");
226 Utf8PathBuf::from(triple_str)
227 };
228
229 for dir in target_paths {
230 let path = dir.join(&triple_filename);
231 if path.is_file() {
232 let path = path.canonicalize_utf8().map_err(|error| {
233 TargetTripleError::TargetPathReadError {
234 source: source.clone(),
235 path,
236 error,
237 }
238 })?;
239 return Self::load_file(
240 triple_str_or_path,
241 &path,
242 source,
243 TargetDefinitionLocation::RustTargetPath(path.clone()),
244 );
245 }
246 }
247
248 let platform = Platform::new(triple_str_or_path.to_owned(), TargetFeatures::Unknown)
254 .map_err(|error| TargetTripleError::TargetSpecError {
255 source: source.clone(),
256 error,
257 })?;
258 Ok(Self {
259 platform,
260 source,
261 location: TargetDefinitionLocation::Heuristic,
262 })
263 }
264
265 pub(super) fn custom_from_path(
267 path: &Utf8Path,
268 source: TargetTripleSource,
269 resolve_dir: &Utf8Path,
270 ) -> Result<Self, TargetTripleError> {
271 assert_eq!(
272 path.extension(),
273 Some("json"),
274 "path {path} must end with .json",
275 );
276 let path = resolve_dir.join(path);
277 let canonicalized_path =
278 path.canonicalize_utf8()
279 .map_err(|error| TargetTripleError::TargetPathReadError {
280 source: source.clone(),
281 path,
282 error,
283 })?;
284 let triple_str = canonicalized_path
286 .file_stem()
287 .expect("target path must not be empty")
288 .to_owned();
289 Self::load_file(
290 &triple_str,
291 &canonicalized_path,
292 source,
293 TargetDefinitionLocation::DirectPath(canonicalized_path.clone()),
294 )
295 }
296
297 fn load_file(
298 triple_str: &str,
299 path: &Utf8Path,
300 source: TargetTripleSource,
301 location: TargetDefinitionLocation,
302 ) -> Result<Self, TargetTripleError> {
303 let contents = std::fs::read_to_string(path).map_err(|error| {
304 TargetTripleError::TargetPathReadError {
305 source: source.clone(),
306 path: path.to_owned(),
307 error,
308 }
309 })?;
310 let platform =
311 Platform::new_custom(triple_str.to_owned(), &contents, TargetFeatures::Unknown)
312 .map_err(|error| TargetTripleError::TargetSpecError {
313 source: source.clone(),
314 error,
315 })?;
316 Ok(Self {
317 platform,
318 source,
319 location,
320 })
321 }
322}
323
324#[derive(Debug)]
332pub enum CargoTargetArg {
333 Builtin(String),
335
336 Path(Utf8PathBuf),
338
339 Extracted(ExtractedCustomPlatform),
341}
342
343impl CargoTargetArg {
344 fn from_custom_json(
345 triple_str: &str,
346 json: &str,
347 source: TargetTripleSource,
348 ) -> Result<Self, TargetTripleError> {
349 let extracted = ExtractedCustomPlatform::new(triple_str, json, source)?;
350 Ok(Self::Extracted(extracted))
351 }
352}
353
354impl fmt::Display for CargoTargetArg {
355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 match self {
357 Self::Builtin(triple) => {
358 write!(f, "{triple}")
359 }
360 Self::Path(path) => {
361 write!(f, "{path}")
362 }
363 Self::Extracted(extracted) => {
364 write!(f, "{}", extracted.path())
365 }
366 }
367 }
368}
369
370#[derive(Clone, Debug, PartialEq, Eq)]
374pub enum TargetTripleSource {
375 CliOption,
377
378 Env,
380
381 CargoConfig {
384 source: CargoConfigSource,
386 },
387
388 Metadata,
391}
392
393impl fmt::Display for TargetTripleSource {
394 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395 match self {
396 Self::CliOption => {
397 write!(f, "--target <option>")
398 }
399 Self::Env => {
400 write!(f, "environment variable `CARGO_BUILD_TARGET`")
401 }
402 Self::CargoConfig {
403 source: CargoConfigSource::CliOption,
404 } => {
405 write!(f, "`build.target` specified by `--config`")
406 }
407
408 Self::CargoConfig {
409 source: CargoConfigSource::File(path),
410 } => {
411 write!(f, "`build.target` within `{path}`")
412 }
413 Self::Metadata => {
414 write!(f, "--archive-file or --binaries-metadata option")
415 }
416 }
417 }
418}
419
420#[derive(Clone, Debug, Eq, PartialEq)]
422pub enum TargetDefinitionLocation {
423 Builtin,
425
426 DirectPath(Utf8PathBuf),
428
429 RustTargetPath(Utf8PathBuf),
431
432 Heuristic,
434
435 MetadataCustom(String),
437}
438
439impl fmt::Display for TargetDefinitionLocation {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 match self {
442 Self::Builtin => {
443 write!(f, "target was builtin")
444 }
445 Self::DirectPath(path) => {
446 write!(f, "definition obtained from file at path `{path}`")
447 }
448 Self::RustTargetPath(path) => {
449 write!(f, "definition obtained from RUST_TARGET_PATH: `{path}`")
450 }
451 Self::Heuristic => {
452 write!(f, "definition obtained heuristically")
453 }
454 Self::MetadataCustom(_) => {
455 write!(f, "custom definition stored in metadata")
456 }
457 }
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464 use crate::cargo_config::test_helpers::{custom_platform, setup_temp_dir};
465
466 #[test]
467 fn test_find_target_triple() {
468 let dir = setup_temp_dir().unwrap();
469 let dir_path = Utf8PathBuf::try_from(dir.path().canonicalize().unwrap()).unwrap();
470 let dir_foo_path = dir_path.join("foo");
471 let dir_foo_bar_path = dir_foo_path.join("bar");
472 let dir_foo_bar_custom1_path = dir_foo_bar_path.join("custom1");
473 let dir_foo_bar_custom2_path = dir_foo_bar_path.join("custom2");
474 let custom_target_dir = dir.path().join("custom-target");
475 let custom_target_path = dir
476 .path()
477 .join("custom-target/my-target.json")
478 .canonicalize_utf8()
479 .expect("path exists");
480
481 assert_eq!(
483 find_target_triple(&[], None, &dir_foo_bar_path, &dir_path),
484 Some(TargetTriple {
485 platform: platform("x86_64-unknown-linux-gnu"),
486 source: TargetTripleSource::CargoConfig {
487 source: CargoConfigSource::File(dir_path.join("foo/bar/.cargo/config.toml")),
488 },
489 location: TargetDefinitionLocation::Builtin,
490 }),
491 );
492
493 assert_eq!(
494 find_target_triple(&[], None, &dir_foo_path, &dir_path),
495 Some(TargetTriple {
496 platform: platform("x86_64-pc-windows-msvc"),
497 source: TargetTripleSource::CargoConfig {
498 source: CargoConfigSource::File(dir_path.join("foo/.cargo/config")),
499 },
500 location: TargetDefinitionLocation::Builtin,
501 }),
502 );
503
504 assert_eq!(
505 find_target_triple(&[], None, &dir_foo_bar_custom2_path, &dir_path),
506 Some(TargetTriple {
507 platform: custom_platform(),
508 source: TargetTripleSource::CargoConfig {
509 source: CargoConfigSource::File(
510 dir_path.join("foo/bar/custom2/.cargo/config.toml")
511 ),
512 },
513 location: TargetDefinitionLocation::DirectPath(custom_target_path.clone()),
514 })
515 );
516
517 assert_eq!(
518 find_target_triple_with_paths(
519 &[],
520 None,
521 &dir_foo_bar_custom1_path,
522 &dir_path,
523 vec![custom_target_dir]
524 ),
525 Some(TargetTriple {
526 platform: custom_platform(),
527 source: TargetTripleSource::CargoConfig {
528 source: CargoConfigSource::File(
529 dir_path.join("foo/bar/custom1/.cargo/config.toml")
530 ),
531 },
532 location: TargetDefinitionLocation::RustTargetPath(custom_target_path.clone()),
533 })
534 );
535
536 assert_eq!(
537 find_target_triple(
538 &["build.target=\"aarch64-unknown-linux-gnu\""],
539 None,
540 &dir_foo_bar_path,
541 &dir_path
542 ),
543 Some(TargetTriple {
544 platform: platform("aarch64-unknown-linux-gnu"),
545 source: TargetTripleSource::CargoConfig {
546 source: CargoConfigSource::CliOption,
547 },
548 location: TargetDefinitionLocation::Builtin,
549 })
550 );
551
552 assert_eq!(
554 find_target_triple(
555 &[
556 "build.target=\"aarch64-unknown-linux-gnu\"",
557 "build.target=\"x86_64-unknown-linux-musl\""
558 ],
559 None,
560 &dir_foo_bar_path,
561 &dir_path
562 ),
563 Some(TargetTriple {
564 platform: platform("aarch64-unknown-linux-gnu"),
565 source: TargetTripleSource::CargoConfig {
566 source: CargoConfigSource::CliOption,
567 },
568 location: TargetDefinitionLocation::Builtin,
569 })
570 );
571
572 assert_eq!(
574 find_target_triple(
575 &["build.target=\"../../custom-target/my-target.json\"",],
576 None,
577 &dir_foo_bar_path,
578 &dir_path
579 ),
580 Some(TargetTriple {
581 platform: custom_platform(),
582 source: TargetTripleSource::CargoConfig {
583 source: CargoConfigSource::CliOption,
584 },
585 location: TargetDefinitionLocation::DirectPath(custom_target_path.clone()),
586 })
587 );
588
589 assert_eq!(
591 find_target_triple(
592 &["build.target=\"aarch64-unknown-linux-gnu\"",],
593 Some("aarch64-pc-windows-msvc"),
594 &dir_foo_bar_path,
595 &dir_path
596 ),
597 Some(TargetTriple {
598 platform: platform("aarch64-unknown-linux-gnu"),
599 source: TargetTripleSource::CargoConfig {
600 source: CargoConfigSource::CliOption,
601 },
602 location: TargetDefinitionLocation::Builtin,
603 })
604 );
605
606 assert_eq!(
608 find_target_triple(
609 &[],
610 Some("aarch64-pc-windows-msvc"),
611 &dir_foo_bar_path,
612 &dir_path
613 ),
614 Some(TargetTriple {
615 platform: platform("aarch64-pc-windows-msvc"),
616 source: TargetTripleSource::Env,
617 location: TargetDefinitionLocation::Builtin,
618 })
619 );
620
621 assert_eq!(
626 find_target_triple(&["extra-config.toml"], None, &dir_foo_path, &dir_path),
627 Some(TargetTriple {
628 platform: platform("aarch64-unknown-linux-gnu"),
629 source: TargetTripleSource::CargoConfig {
630 source: CargoConfigSource::File(dir_foo_path.join("extra-config.toml")),
631 },
632 location: TargetDefinitionLocation::Builtin,
633 })
634 );
635 assert_eq!(
636 find_target_triple(
637 &["extra-config.toml"],
638 Some("aarch64-pc-windows-msvc"),
639 &dir_foo_path,
640 &dir_path
641 ),
642 Some(TargetTriple {
643 platform: platform("aarch64-unknown-linux-gnu"),
644 source: TargetTripleSource::CargoConfig {
645 source: CargoConfigSource::File(dir_foo_path.join("extra-config.toml")),
646 },
647 location: TargetDefinitionLocation::Builtin,
648 })
649 );
650 assert_eq!(
651 find_target_triple(
652 &[
653 "../extra-config.toml",
654 "build.target=\"x86_64-unknown-linux-musl\"",
655 ],
656 None,
657 &dir_foo_bar_path,
658 &dir_path
659 ),
660 Some(TargetTriple {
661 platform: platform("x86_64-unknown-linux-musl"),
662 source: TargetTripleSource::CargoConfig {
663 source: CargoConfigSource::CliOption,
664 },
665 location: TargetDefinitionLocation::Builtin,
666 })
667 );
668 assert_eq!(
669 find_target_triple(
670 &[
671 "build.target=\"x86_64-unknown-linux-musl\"",
672 "extra-config.toml",
673 ],
674 None,
675 &dir_foo_path,
676 &dir_path
677 ),
678 Some(TargetTriple {
679 platform: platform("x86_64-unknown-linux-musl"),
680 source: TargetTripleSource::CargoConfig {
681 source: CargoConfigSource::CliOption,
682 },
683 location: TargetDefinitionLocation::Builtin,
684 })
685 );
686 assert_eq!(
690 find_target_triple(
691 &["../extra-custom-config.toml"],
692 None,
693 &dir_foo_bar_path,
694 &dir_path
695 ),
696 Some(TargetTriple {
697 platform: custom_platform(),
698 source: TargetTripleSource::CargoConfig {
699 source: CargoConfigSource::File(dir_foo_path.join("extra-custom-config.toml")),
700 },
701 location: TargetDefinitionLocation::DirectPath(custom_target_path),
702 })
703 );
704
705 assert_eq!(find_target_triple(&[], None, &dir_path, &dir_path), None);
706 }
707
708 fn find_target_triple(
709 cli_configs: &[&str],
710 env: Option<&str>,
711 start_search_at: &Utf8Path,
712 terminate_search_at: &Utf8Path,
713 ) -> Option<TargetTriple> {
714 find_target_triple_with_paths(
715 cli_configs,
716 env,
717 start_search_at,
718 terminate_search_at,
719 Vec::new(),
720 )
721 }
722
723 fn find_target_triple_with_paths(
724 cli_configs: &[&str],
725 env: Option<&str>,
726 start_search_at: &Utf8Path,
727 terminate_search_at: &Utf8Path,
728 target_paths: Vec<Utf8PathBuf>,
729 ) -> Option<TargetTriple> {
730 find_target_triple_impl(
731 cli_configs,
732 None,
733 env,
734 start_search_at,
735 terminate_search_at,
736 target_paths,
737 &dummy_host_platform(),
738 )
739 }
740
741 fn find_target_triple_with_host(
742 cli_configs: &[&str],
743 target_cli_option: Option<&str>,
744 env: Option<&str>,
745 start_search_at: &Utf8Path,
746 terminate_search_at: &Utf8Path,
747 host_platform: &Platform,
748 ) -> Option<TargetTriple> {
749 find_target_triple_impl(
750 cli_configs,
751 target_cli_option,
752 env,
753 start_search_at,
754 terminate_search_at,
755 Vec::new(),
756 host_platform,
757 )
758 }
759
760 fn find_target_triple_impl(
761 cli_configs: &[&str],
762 target_cli_option: Option<&str>,
763 env: Option<&str>,
764 start_search_at: &Utf8Path,
765 terminate_search_at: &Utf8Path,
766 target_paths: Vec<Utf8PathBuf>,
767 host_platform: &Platform,
768 ) -> Option<TargetTriple> {
769 let configs = CargoConfigs::new_with_isolation(
770 cli_configs,
771 start_search_at,
772 terminate_search_at,
773 target_paths,
774 )
775 .unwrap();
776 if let Some(env) = env {
777 unsafe { std::env::set_var("CARGO_BUILD_TARGET", env) };
780 }
781 let ret = TargetTriple::find(&configs, target_cli_option, host_platform).unwrap();
782 unsafe { std::env::remove_var("CARGO_BUILD_TARGET") };
785 ret
786 }
787
788 #[test]
789 fn test_host_tuple() {
790 let dir = camino_tempfile::Builder::new()
792 .tempdir()
793 .expect("error creating tempdir");
794 let dir_path = Utf8PathBuf::try_from(dir.path().canonicalize().unwrap()).unwrap();
795
796 std::fs::create_dir_all(dir.path().join(".cargo")).expect("error creating .cargo subdir");
797 std::fs::write(
798 dir.path().join(".cargo/config.toml"),
799 r#"
800 [build]
801 target = "host-tuple"
802 "#,
803 )
804 .expect("error writing .cargo/config.toml");
805
806 let host_platform = platform("aarch64-apple-darwin");
807
808 assert_eq!(
810 find_target_triple_with_host(
811 &[],
812 Some("host-tuple"),
813 None,
814 &dir_path,
815 &dir_path,
816 &host_platform,
817 ),
818 Some(TargetTriple {
819 platform: platform("aarch64-apple-darwin"),
820 source: TargetTripleSource::CliOption,
821 location: TargetDefinitionLocation::Builtin,
822 })
823 );
824
825 assert_eq!(
827 find_target_triple_with_host(
828 &["build.target=\"host-tuple\""],
829 None,
830 None,
831 &dir_path,
832 &dir_path,
833 &host_platform,
834 ),
835 Some(TargetTriple {
836 platform: platform("aarch64-apple-darwin"),
837 source: TargetTripleSource::CargoConfig {
838 source: CargoConfigSource::CliOption,
839 },
840 location: TargetDefinitionLocation::Builtin,
841 })
842 );
843
844 assert_eq!(
846 find_target_triple_with_host(
847 &[],
848 None,
849 Some("host-tuple"),
850 &dir_path,
851 &dir_path,
852 &host_platform,
853 ),
854 Some(TargetTriple {
855 platform: platform("aarch64-apple-darwin"),
856 source: TargetTripleSource::Env,
857 location: TargetDefinitionLocation::Builtin,
858 })
859 );
860
861 assert_eq!(
863 find_target_triple_with_host(&[], None, None, &dir_path, &dir_path, &host_platform),
864 Some(TargetTriple {
865 platform: platform("aarch64-apple-darwin"),
866 source: TargetTripleSource::CargoConfig {
867 source: CargoConfigSource::File(dir_path.join(".cargo/config.toml")),
868 },
869 location: TargetDefinitionLocation::Builtin,
870 })
871 );
872 }
873
874 fn platform(triple_str: &str) -> Platform {
875 Platform::new(triple_str.to_owned(), TargetFeatures::Unknown).expect("triple str is valid")
876 }
877
878 fn dummy_host_platform() -> Platform {
879 Platform::new(
880 "x86_64-unknown-linux-gnu".to_owned(),
881 TargetFeatures::Unknown,
882 )
883 .unwrap()
884 }
885}