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(
101 cargo_configs: &CargoConfigs,
102 target_cli_option: Option<&str>,
103 ) -> Result<Option<Self>, TargetTripleError> {
104 if let Some(triple_str_or_path) = target_cli_option {
106 let ret = Self::resolve_triple(
107 triple_str_or_path,
108 TargetTripleSource::CliOption,
109 cargo_configs.cwd(),
110 cargo_configs.target_paths(),
111 )?;
112 return Ok(Some(ret));
113 }
114
115 Self::from_cargo_configs(cargo_configs)
117 }
118
119 pub const CARGO_BUILD_TARGET_ENV: &'static str = "CARGO_BUILD_TARGET";
121
122 fn from_env(
123 cwd: &Utf8Path,
124 target_paths: &[Utf8PathBuf],
125 ) -> Result<Option<Self>, TargetTripleError> {
126 if let Some(triple_val) = std::env::var_os(Self::CARGO_BUILD_TARGET_ENV) {
127 let triple = triple_val
128 .into_string()
129 .map_err(|_osstr| TargetTripleError::InvalidEnvironmentVar)?;
130 let ret = Self::resolve_triple(&triple, TargetTripleSource::Env, cwd, target_paths)?;
131 Ok(Some(ret))
132 } else {
133 Ok(None)
134 }
135 }
136
137 fn from_cargo_configs(cargo_configs: &CargoConfigs) -> Result<Option<Self>, TargetTripleError> {
138 for discovered_config in cargo_configs.discovered_configs() {
139 match discovered_config {
140 DiscoveredConfig::CliOption { config, source }
141 | DiscoveredConfig::File { config, source } => {
142 if let Some(triple) = &config.build.target {
143 let resolve_dir = source.resolve_dir(cargo_configs.cwd());
144 let source = TargetTripleSource::CargoConfig {
145 source: source.clone(),
146 };
147 let ret = Self::resolve_triple(
148 triple,
149 source,
150 resolve_dir,
151 cargo_configs.target_paths(),
152 )?;
153 return Ok(Some(ret));
154 }
155 }
156 DiscoveredConfig::Env => {
157 if let Some(triple) =
159 Self::from_env(cargo_configs.cwd(), cargo_configs.target_paths())?
160 {
161 return Ok(Some(triple));
162 }
163 }
164 }
165 }
166
167 Ok(None)
168 }
169
170 fn resolve_triple(
174 triple_str_or_path: &str,
175 source: TargetTripleSource,
176 resolve_dir: &Utf8Path,
179 target_paths: &[Utf8PathBuf],
180 ) -> Result<Self, TargetTripleError> {
181 if triple_str_or_path.ends_with(".json") {
182 return Self::custom_from_path(triple_str_or_path.as_ref(), source, resolve_dir);
183 }
184
185 if let Ok(platform) =
187 Platform::new_strict(triple_str_or_path.to_owned(), TargetFeatures::Unknown)
188 {
189 return Ok(Self {
190 platform,
191 source,
192 location: TargetDefinitionLocation::Builtin,
193 });
194 }
195
196 let triple_filename = {
198 let mut triple_str = triple_str_or_path.to_owned();
199 triple_str.push_str(".json");
200 Utf8PathBuf::from(triple_str)
201 };
202
203 for dir in target_paths {
204 let path = dir.join(&triple_filename);
205 if path.is_file() {
206 let path = path.canonicalize_utf8().map_err(|error| {
207 TargetTripleError::TargetPathReadError {
208 source: source.clone(),
209 path,
210 error,
211 }
212 })?;
213 return Self::load_file(
214 triple_str_or_path,
215 &path,
216 source,
217 TargetDefinitionLocation::RustTargetPath(path.clone()),
218 );
219 }
220 }
221
222 let platform = Platform::new(triple_str_or_path.to_owned(), TargetFeatures::Unknown)
228 .map_err(|error| TargetTripleError::TargetSpecError {
229 source: source.clone(),
230 error,
231 })?;
232 Ok(Self {
233 platform,
234 source,
235 location: TargetDefinitionLocation::Heuristic,
236 })
237 }
238
239 pub(super) fn custom_from_path(
241 path: &Utf8Path,
242 source: TargetTripleSource,
243 resolve_dir: &Utf8Path,
244 ) -> Result<Self, TargetTripleError> {
245 assert_eq!(
246 path.extension(),
247 Some("json"),
248 "path {path} must end with .json",
249 );
250 let path = resolve_dir.join(path);
251 let canonicalized_path =
252 path.canonicalize_utf8()
253 .map_err(|error| TargetTripleError::TargetPathReadError {
254 source: source.clone(),
255 path,
256 error,
257 })?;
258 let triple_str = canonicalized_path
260 .file_stem()
261 .expect("target path must not be empty")
262 .to_owned();
263 Self::load_file(
264 &triple_str,
265 &canonicalized_path,
266 source,
267 TargetDefinitionLocation::DirectPath(canonicalized_path.clone()),
268 )
269 }
270
271 fn load_file(
272 triple_str: &str,
273 path: &Utf8Path,
274 source: TargetTripleSource,
275 location: TargetDefinitionLocation,
276 ) -> Result<Self, TargetTripleError> {
277 let contents = std::fs::read_to_string(path).map_err(|error| {
278 TargetTripleError::TargetPathReadError {
279 source: source.clone(),
280 path: path.to_owned(),
281 error,
282 }
283 })?;
284 let platform =
285 Platform::new_custom(triple_str.to_owned(), &contents, TargetFeatures::Unknown)
286 .map_err(|error| TargetTripleError::TargetSpecError {
287 source: source.clone(),
288 error,
289 })?;
290 Ok(Self {
291 platform,
292 source,
293 location,
294 })
295 }
296}
297
298#[derive(Debug)]
306pub enum CargoTargetArg {
307 Builtin(String),
309
310 Path(Utf8PathBuf),
312
313 Extracted(ExtractedCustomPlatform),
315}
316
317impl CargoTargetArg {
318 fn from_custom_json(
319 triple_str: &str,
320 json: &str,
321 source: TargetTripleSource,
322 ) -> Result<Self, TargetTripleError> {
323 let extracted = ExtractedCustomPlatform::new(triple_str, json, source)?;
324 Ok(Self::Extracted(extracted))
325 }
326}
327
328impl fmt::Display for CargoTargetArg {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 match self {
331 Self::Builtin(triple) => {
332 write!(f, "{triple}")
333 }
334 Self::Path(path) => {
335 write!(f, "{path}")
336 }
337 Self::Extracted(extracted) => {
338 write!(f, "{}", extracted.path())
339 }
340 }
341 }
342}
343
344#[derive(Clone, Debug, PartialEq, Eq)]
348pub enum TargetTripleSource {
349 CliOption,
351
352 Env,
354
355 CargoConfig {
358 source: CargoConfigSource,
360 },
361
362 Metadata,
365}
366
367impl fmt::Display for TargetTripleSource {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 match self {
370 Self::CliOption => {
371 write!(f, "--target <option>")
372 }
373 Self::Env => {
374 write!(f, "environment variable `CARGO_BUILD_TARGET`")
375 }
376 Self::CargoConfig {
377 source: CargoConfigSource::CliOption,
378 } => {
379 write!(f, "`build.target` specified by `--config`")
380 }
381
382 Self::CargoConfig {
383 source: CargoConfigSource::File(path),
384 } => {
385 write!(f, "`build.target` within `{path}`")
386 }
387 Self::Metadata => {
388 write!(f, "--archive-file or --binaries-metadata option")
389 }
390 }
391 }
392}
393
394#[derive(Clone, Debug, Eq, PartialEq)]
396pub enum TargetDefinitionLocation {
397 Builtin,
399
400 DirectPath(Utf8PathBuf),
402
403 RustTargetPath(Utf8PathBuf),
405
406 Heuristic,
408
409 MetadataCustom(String),
411}
412
413impl fmt::Display for TargetDefinitionLocation {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 match self {
416 Self::Builtin => {
417 write!(f, "target was builtin")
418 }
419 Self::DirectPath(path) => {
420 write!(f, "definition obtained from file at path `{path}`")
421 }
422 Self::RustTargetPath(path) => {
423 write!(f, "definition obtained from RUST_TARGET_PATH: `{path}`")
424 }
425 Self::Heuristic => {
426 write!(f, "definition obtained heuristically")
427 }
428 Self::MetadataCustom(_) => {
429 write!(f, "custom definition stored in metadata")
430 }
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use crate::cargo_config::test_helpers::{custom_platform, setup_temp_dir};
439
440 #[test]
441 fn test_find_target_triple() {
442 let dir = setup_temp_dir().unwrap();
443 let dir_path = Utf8PathBuf::try_from(dir.path().canonicalize().unwrap()).unwrap();
444 let dir_foo_path = dir_path.join("foo");
445 let dir_foo_bar_path = dir_foo_path.join("bar");
446 let dir_foo_bar_custom1_path = dir_foo_bar_path.join("custom1");
447 let dir_foo_bar_custom2_path = dir_foo_bar_path.join("custom2");
448 let custom_target_dir = dir.path().join("custom-target");
449 let custom_target_path = dir
450 .path()
451 .join("custom-target/my-target.json")
452 .canonicalize_utf8()
453 .expect("path exists");
454
455 assert_eq!(
457 find_target_triple(&[], None, &dir_foo_bar_path, &dir_path),
458 Some(TargetTriple {
459 platform: platform("x86_64-unknown-linux-gnu"),
460 source: TargetTripleSource::CargoConfig {
461 source: CargoConfigSource::File(dir_path.join("foo/bar/.cargo/config.toml")),
462 },
463 location: TargetDefinitionLocation::Builtin,
464 }),
465 );
466
467 assert_eq!(
468 find_target_triple(&[], None, &dir_foo_path, &dir_path),
469 Some(TargetTriple {
470 platform: platform("x86_64-pc-windows-msvc"),
471 source: TargetTripleSource::CargoConfig {
472 source: CargoConfigSource::File(dir_path.join("foo/.cargo/config")),
473 },
474 location: TargetDefinitionLocation::Builtin,
475 }),
476 );
477
478 assert_eq!(
479 find_target_triple(&[], None, &dir_foo_bar_custom2_path, &dir_path),
480 Some(TargetTriple {
481 platform: custom_platform(),
482 source: TargetTripleSource::CargoConfig {
483 source: CargoConfigSource::File(
484 dir_path.join("foo/bar/custom2/.cargo/config.toml")
485 ),
486 },
487 location: TargetDefinitionLocation::DirectPath(custom_target_path.clone()),
488 })
489 );
490
491 assert_eq!(
492 find_target_triple_with_paths(
493 &[],
494 None,
495 &dir_foo_bar_custom1_path,
496 &dir_path,
497 vec![custom_target_dir]
498 ),
499 Some(TargetTriple {
500 platform: custom_platform(),
501 source: TargetTripleSource::CargoConfig {
502 source: CargoConfigSource::File(
503 dir_path.join("foo/bar/custom1/.cargo/config.toml")
504 ),
505 },
506 location: TargetDefinitionLocation::RustTargetPath(custom_target_path.clone()),
507 })
508 );
509
510 assert_eq!(
511 find_target_triple(
512 &["build.target=\"aarch64-unknown-linux-gnu\""],
513 None,
514 &dir_foo_bar_path,
515 &dir_path
516 ),
517 Some(TargetTriple {
518 platform: platform("aarch64-unknown-linux-gnu"),
519 source: TargetTripleSource::CargoConfig {
520 source: CargoConfigSource::CliOption,
521 },
522 location: TargetDefinitionLocation::Builtin,
523 })
524 );
525
526 assert_eq!(
528 find_target_triple(
529 &[
530 "build.target=\"aarch64-unknown-linux-gnu\"",
531 "build.target=\"x86_64-unknown-linux-musl\""
532 ],
533 None,
534 &dir_foo_bar_path,
535 &dir_path
536 ),
537 Some(TargetTriple {
538 platform: platform("aarch64-unknown-linux-gnu"),
539 source: TargetTripleSource::CargoConfig {
540 source: CargoConfigSource::CliOption,
541 },
542 location: TargetDefinitionLocation::Builtin,
543 })
544 );
545
546 assert_eq!(
548 find_target_triple(
549 &["build.target=\"../../custom-target/my-target.json\"",],
550 None,
551 &dir_foo_bar_path,
552 &dir_path
553 ),
554 Some(TargetTriple {
555 platform: custom_platform(),
556 source: TargetTripleSource::CargoConfig {
557 source: CargoConfigSource::CliOption,
558 },
559 location: TargetDefinitionLocation::DirectPath(custom_target_path.clone()),
560 })
561 );
562
563 assert_eq!(
565 find_target_triple(
566 &["build.target=\"aarch64-unknown-linux-gnu\"",],
567 Some("aarch64-pc-windows-msvc"),
568 &dir_foo_bar_path,
569 &dir_path
570 ),
571 Some(TargetTriple {
572 platform: platform("aarch64-unknown-linux-gnu"),
573 source: TargetTripleSource::CargoConfig {
574 source: CargoConfigSource::CliOption,
575 },
576 location: TargetDefinitionLocation::Builtin,
577 })
578 );
579
580 assert_eq!(
582 find_target_triple(
583 &[],
584 Some("aarch64-pc-windows-msvc"),
585 &dir_foo_bar_path,
586 &dir_path
587 ),
588 Some(TargetTriple {
589 platform: platform("aarch64-pc-windows-msvc"),
590 source: TargetTripleSource::Env,
591 location: TargetDefinitionLocation::Builtin,
592 })
593 );
594
595 assert_eq!(
600 find_target_triple(&["extra-config.toml"], None, &dir_foo_path, &dir_path),
601 Some(TargetTriple {
602 platform: platform("aarch64-unknown-linux-gnu"),
603 source: TargetTripleSource::CargoConfig {
604 source: CargoConfigSource::File(dir_foo_path.join("extra-config.toml")),
605 },
606 location: TargetDefinitionLocation::Builtin,
607 })
608 );
609 assert_eq!(
610 find_target_triple(
611 &["extra-config.toml"],
612 Some("aarch64-pc-windows-msvc"),
613 &dir_foo_path,
614 &dir_path
615 ),
616 Some(TargetTriple {
617 platform: platform("aarch64-unknown-linux-gnu"),
618 source: TargetTripleSource::CargoConfig {
619 source: CargoConfigSource::File(dir_foo_path.join("extra-config.toml")),
620 },
621 location: TargetDefinitionLocation::Builtin,
622 })
623 );
624 assert_eq!(
625 find_target_triple(
626 &[
627 "../extra-config.toml",
628 "build.target=\"x86_64-unknown-linux-musl\"",
629 ],
630 None,
631 &dir_foo_bar_path,
632 &dir_path
633 ),
634 Some(TargetTriple {
635 platform: platform("x86_64-unknown-linux-musl"),
636 source: TargetTripleSource::CargoConfig {
637 source: CargoConfigSource::CliOption,
638 },
639 location: TargetDefinitionLocation::Builtin,
640 })
641 );
642 assert_eq!(
643 find_target_triple(
644 &[
645 "build.target=\"x86_64-unknown-linux-musl\"",
646 "extra-config.toml",
647 ],
648 None,
649 &dir_foo_path,
650 &dir_path
651 ),
652 Some(TargetTriple {
653 platform: platform("x86_64-unknown-linux-musl"),
654 source: TargetTripleSource::CargoConfig {
655 source: CargoConfigSource::CliOption,
656 },
657 location: TargetDefinitionLocation::Builtin,
658 })
659 );
660 assert_eq!(
664 find_target_triple(
665 &["../extra-custom-config.toml"],
666 None,
667 &dir_foo_bar_path,
668 &dir_path
669 ),
670 Some(TargetTriple {
671 platform: custom_platform(),
672 source: TargetTripleSource::CargoConfig {
673 source: CargoConfigSource::File(dir_foo_path.join("extra-custom-config.toml")),
674 },
675 location: TargetDefinitionLocation::DirectPath(custom_target_path),
676 })
677 );
678
679 assert_eq!(find_target_triple(&[], None, &dir_path, &dir_path), None);
680 }
681
682 fn find_target_triple(
683 cli_configs: &[&str],
684 env: Option<&str>,
685 start_search_at: &Utf8Path,
686 terminate_search_at: &Utf8Path,
687 ) -> Option<TargetTriple> {
688 find_target_triple_with_paths(
689 cli_configs,
690 env,
691 start_search_at,
692 terminate_search_at,
693 Vec::new(),
694 )
695 }
696
697 fn find_target_triple_with_paths(
698 cli_configs: &[&str],
699 env: Option<&str>,
700 start_search_at: &Utf8Path,
701 terminate_search_at: &Utf8Path,
702 target_paths: Vec<Utf8PathBuf>,
703 ) -> Option<TargetTriple> {
704 let configs = CargoConfigs::new_with_isolation(
705 cli_configs,
706 start_search_at,
707 terminate_search_at,
708 target_paths,
709 )
710 .unwrap();
711 if let Some(env) = env {
712 std::env::set_var("CARGO_BUILD_TARGET", env);
713 }
714 let ret = TargetTriple::from_cargo_configs(&configs).unwrap();
715 std::env::remove_var("CARGO_BUILD_TARGET");
716 ret
717 }
718
719 fn platform(triple_str: &str) -> Platform {
720 Platform::new(triple_str.to_owned(), TargetFeatures::Unknown).expect("triple str is valid")
721 }
722}