Skip to main content

target_spec_json/
lib.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/*!
4<!-- Note: Document from sync-markdown-to-rustdoc:start through sync-markdown-to-rustdoc:end
5     is synchronized from README.md. Any changes to that range are not preserved. -->
6<!-- tidy:sync-markdown-to-rustdoc:start -->
7
8Structured access to rustc `--print target-spec-json` and `--print all-target-specs-json`.
9
10## Usage
11
12Add this to your `Cargo.toml`:
13
14```toml
15[dependencies]
16target-spec-json = "0.2"
17```
18
19## Compatibility
20
21Both `--print target-spec-json` and `--print all-target-specs-json` are unstable interfaces and may not work with certain version combinations of Rust versions and `target-spec-json` versions.
22
23The following combinations have been confirmed to work:
24
25| target-spec-json | Rust                                    |
26| ---------------- | --------------------------------------- |
27| 0.2.13           | nightly-2026-09-06                      |
28| 0.2.12           | nightly-2026-07-23 - nightly-2026-09-05 |
29| 0.2.11           | nightly-2026-07-03 - nightly-2026-07-20 |
30| 0.2.10           | nightly-2026-06-27 - nightly-2026-06-28 |
31| 0.2.9            | nightly-2026-04-29 - nightly-2026-06-26 |
32| 0.2.8            | nightly-2026-03-26 - nightly-2026-04-29 |
33| 0.2.7            | nightly-2026-01-09 - nightly-2026-03-25 |
34| 0.2.6            | nightly-2025-11-29 - nightly-2026-01-08 |
35| 0.2.5            | nightly-2025-10-08 - nightly-2025-10-30 |
36| 0.2.4            | nightly-2025-09-23 - nightly-2025-10-07 |
37| 0.2.3            | nightly-2025-09-01 - nightly-2025-09-22 |
38| 0.2.2            | nightly-2025-08-31                      |
39| 0.2.1            | nightly-2025-08-10 - nightly-2025-08-30 |
40| 0.2.0            | nightly-2025-07-06 - nightly-2025-08-08 |
41
42## Related Projects
43
44- [cargo-config2]: Library to load and resolve Cargo configuration.
45
46[cargo-config2]: https://github.com/taiki-e/cargo-config2
47
48<!-- tidy:sync-markdown-to-rustdoc:end -->
49*/
50
51#![no_std]
52#![doc(test(
53    no_crate_inject,
54    attr(allow(
55        dead_code,
56        unused_variables,
57        clippy::undocumented_unsafe_blocks,
58        clippy::unused_trait_names,
59    ))
60))]
61#![forbid(unsafe_code)]
62#![warn(
63    // Lints that may help when writing public library.
64    missing_debug_implementations,
65    // missing_docs,
66    clippy::alloc_instead_of_core,
67    clippy::exhaustive_enums,
68    clippy::exhaustive_structs,
69    clippy::impl_trait_in_params,
70    clippy::std_instead_of_alloc,
71    clippy::std_instead_of_core,
72    // clippy::missing_inline_in_public_items,
73)]
74
75extern crate alloc;
76extern crate std;
77
78#[cfg(test)]
79#[path = "gen/tests/assert_impl.rs"]
80mod assert_impl;
81#[cfg(test)]
82#[path = "gen/tests/track_size.rs"]
83mod track_size;
84
85#[path = "gen/target_spec.rs"]
86mod target_spec;
87pub use self::target_spec::{
88    Arch, BinaryFormat, Env, Os, PanicStrategy, Sanitizer, TargetEndian, TargetFamily,
89};
90
91#[macro_use]
92mod process;
93
94mod error;
95
96use alloc::{collections::BTreeMap, string::String, vec::Vec};
97use core::ops;
98use std::process::Command;
99
100use serde_derive::{Deserialize, Serialize};
101
102pub use self::error::Error;
103use self::{error::Result, process::ProcessBuilder};
104
105pub type AllTargetSpecs = BTreeMap<String, TargetSpec>;
106
107// Refs:
108// - https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_target/src/spec/mod.rs
109// - https://github.com/rust-lang/rust/blob/c0bb3b98bb7aac24a37635e5d36d961e0b14f435/compiler/rustc_target/src/spec/json.rs
110// TODO: use https://github.com/rust-lang/rust/pull/144498
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "kebab-case")]
114#[cfg_attr(test, serde(deny_unknown_fields))]
115#[non_exhaustive]
116pub struct TargetSpec {
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub abi_return_struct_as_int: Option<bool>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub abi: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub allows_weak_linkage: Option<bool>,
123    pub arch: Arch,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub archive_format: Option<String>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub asm_args: Option<Vec<String>>,
128    #[serde(default = "default_true", skip_serializing_if = "Clone::clone")]
129    pub atomic_cas: bool,
130    #[serde(default, skip_serializing_if = "BinaryFormat::is_elf")]
131    pub binary_format: BinaryFormat,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub bitcode_llvm_cmdline: Option<String>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub c_enum_min_bits: Option<u32>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub code_model: Option<String>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub cpu: Option<String>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub crt_objects_fallback: Option<String>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub crt_static_allows_dylibs: Option<bool>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub crt_static_default: Option<bool>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub crt_static_respected: Option<bool>,
148    pub data_layout: String,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub debuginfo_kind: Option<String>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub default_adjusted_cabi: Option<String>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub default_codegen_units: Option<u32>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub default_dwarf_version: Option<u32>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub default_hidden_visibility: Option<bool>,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub default_sanitizers: Option<Vec<Sanitizer>>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub default_uwtable: Option<bool>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub direct_access_external_data: Option<bool>,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub disable_redzone: Option<bool>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub dll_prefix: Option<String>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub dll_suffix: Option<String>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub dll_tls_export: Option<bool>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub dynamic_linking: Option<bool>,
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub eh_frame_header: Option<bool>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub emit_debug_gdb_scripts: Option<bool>,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub entry_abi: Option<String>,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub entry_name: Option<String>,
183    #[serde(default, skip_serializing_if = "Env::is_none")]
184    pub env: Env,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub executables: Option<bool>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub exe_suffix: Option<String>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub features: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub force_emulated_tls: Option<bool>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub forces_embed_bitcode: Option<bool>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub frame_pointer: Option<String>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub function_sections: Option<bool>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub generate_arange_section: Option<bool>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub has_rpath: Option<bool>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub has_thread_local: Option<bool>,
205    #[serde(default, skip_serializing_if = "ops::Not::not")]
206    pub has_thumb_interworking: bool,
207    #[serde(default, skip_serializing_if = "ops::Not::not")]
208    pub is_builtin: bool,
209    #[serde(default, skip_serializing_if = "ops::Not::not")]
210    pub is_like_android: bool,
211    #[serde(default, skip_serializing_if = "ops::Not::not")]
212    pub is_like_aix: bool,
213    #[serde(default, skip_serializing_if = "ops::Not::not")]
214    pub is_like_darwin: bool,
215    #[serde(default, skip_serializing_if = "ops::Not::not")]
216    pub is_like_gpu: bool,
217    /// replaced by `is_like_darwin`
218    #[serde(default, skip_serializing_if = "ops::Not::not")]
219    pub is_like_osx: bool,
220    #[serde(default, skip_serializing_if = "ops::Not::not")]
221    pub is_like_solaris: bool,
222    #[serde(default, skip_serializing_if = "ops::Not::not")]
223    pub is_like_msvc: bool,
224    #[serde(default, skip_serializing_if = "ops::Not::not")]
225    pub is_like_vexos: bool,
226    #[serde(default, skip_serializing_if = "ops::Not::not")]
227    pub is_like_wasm: bool,
228    #[serde(default, skip_serializing_if = "ops::Not::not")]
229    pub is_like_windows: bool,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub late_link_args: Option<BTreeMap<String, Vec<String>>>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub late_link_args_dynamic: Option<BTreeMap<String, Vec<String>>>,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub late_link_args_static: Option<BTreeMap<String, Vec<String>>>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub limit_rdylib_exports: Option<bool>,
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub link_env: Option<Vec<String>>,
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub link_env_remove: Option<Vec<String>>,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub link_self_contained: Option<BTreeMap<String, Vec<String>>>,
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub link_script: Option<String>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub linker: Option<String>,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub linker_flavor: Option<String>,
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub linker_is_gnu: Option<bool>,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub lld_flavor: Option<String>,
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub llvm_abiname: Option<String>,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub llvm_args: Option<Vec<String>>,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub llvm_floatabi: Option<String>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub llvm_mcount_intrinsic: Option<String>,
262    pub llvm_target: String,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub main_needs_argc_argv: Option<bool>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub max_atomic_width: Option<u32>,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub merge_functions: Option<String>,
269    pub metadata: Option<Metadata>,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub min_atomic_width: Option<u32>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub min_global_align: Option<u32>,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub need_explicit_cpu: Option<bool>,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub needs_plt: Option<bool>,
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub no_builtins: Option<bool>,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub no_default_libraries: Option<bool>,
282    #[serde(default, skip_serializing_if = "Os::is_none")]
283    pub os: Os,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub obj_is_bitcode: Option<bool>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub only_cdylib: Option<bool>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub override_export_symbols: Option<Vec<String>>,
290    #[serde(default, skip_serializing_if = "PanicStrategy::is_unwind")]
291    pub panic_strategy: PanicStrategy,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub plt_by_default: Option<bool>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub position_independent_executables: Option<bool>,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub post_link_args: Option<BTreeMap<String, Vec<String>>>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub post_link_objects: Option<BTreeMap<String, Vec<String>>>,
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub post_link_objects_fallback: Option<BTreeMap<String, Vec<String>>>,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub pre_link_args: Option<BTreeMap<String, Vec<String>>>,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub pre_link_objects: Option<BTreeMap<String, Vec<String>>>,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub pre_link_objects_fallback: Option<BTreeMap<String, Vec<String>>>,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub relax_elf_relocations: Option<bool>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub relocation_model: Option<String>,
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub relro_level: Option<String>,
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub requires_consistent_cpu: Option<bool>,
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub requires_lto: Option<bool>,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub requires_uwtable: Option<bool>,
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub rustc_abi: Option<String>,
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub split_debuginfo: Option<String>,
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub stack_probes: Option<StackProbes>,
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub static_initializer_must_be_acyclic: Option<bool>,
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub static_position_independent_executables: Option<bool>,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub staticlib_prefix: Option<String>,
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub staticlib_suffix: Option<String>,
334    #[serde(default, skip_serializing_if = "Vec::is_empty")]
335    pub supported_sanitizers: Vec<Sanitizer>,
336    #[serde(default, skip_serializing_if = "Vec::is_empty")]
337    pub supported_split_debuginfo: Vec<String>,
338    #[serde(default = "default_true", skip_serializing_if = "Clone::clone")]
339    pub supports_stack_protector: bool,
340    #[serde(default, skip_serializing_if = "ops::Not::not")]
341    pub supports_fentry: bool,
342    #[serde(default, skip_serializing_if = "ops::Not::not")]
343    pub supports_xray: bool,
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub simd_types_indirect: Option<bool>,
346    #[serde(default, skip_serializing_if = "ops::Not::not")]
347    pub singlethread: bool,
348    #[serde(default, skip_serializing_if = "TargetEndian::is_little")]
349    pub target_endian: TargetEndian,
350    #[serde(default, skip_serializing_if = "Vec::is_empty")]
351    pub target_family: Vec<TargetFamily>,
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub target_mcount: Option<String>,
354    // Integer since 1.89
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub target_c_int_width: Option<u32>,
357    // Integer since 1.91
358    pub target_pointer_width: u32,
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub tls_model: Option<String>,
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub trap_unreachable: Option<bool>,
363    #[serde(default, skip_serializing_if = "Vec::is_empty")]
364    pub unsupported_cpus: Vec<String>,
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub use_ctors_section: Option<bool>,
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub vendor: Option<String>,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
372#[cfg_attr(test, serde(deny_unknown_fields))]
373#[non_exhaustive]
374pub struct Metadata {
375    pub description: Option<String>,
376    pub host_tools: Option<bool>,
377    pub std: Option<bool>,
378    pub tier: Option<u32>,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
382#[serde(rename_all = "kebab-case")]
383#[cfg_attr(test, serde(deny_unknown_fields))]
384#[non_exhaustive]
385pub struct StackProbes {
386    pub kind: String,
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub min_llvm_version_for_inline: Option<(u32, u32, u32)>,
389}
390
391fn default_true() -> bool {
392    true
393}
394
395/// `<rustc> -Z unstable-options --print target-spec-json --target <target>`
396pub fn target_spec_json(rustc: Command, target: &str) -> Result<TargetSpec> {
397    let raw = ProcessBuilder::from_std(rustc)
398        .args(["-Z", "unstable-options", "--print", "target-spec-json", "--target", target])
399        .read()?;
400    serde_json::from_str(&raw).map_err(Error::new)
401}
402
403/// `<rustc> -Z unstable-options --print all-target-specs-json`
404pub fn all_target_specs_json(rustc: Command) -> Result<AllTargetSpecs> {
405    let raw = ProcessBuilder::from_std(rustc)
406        .args(["-Z", "unstable-options", "--print", "all-target-specs-json"])
407        .read()?;
408    serde_json::from_str(&raw).map_err(Error::new)
409}
410
411#[cfg(test)]
412mod tests {
413    use std::eprintln;
414
415    use super::*;
416
417    fn target_spec_json(target: &str) -> Result<(TargetSpec, String)> {
418        let mut cmd = cmd!(
419            "rustc",
420            "-Z",
421            "unstable-options",
422            "--print",
423            "target-spec-json",
424            "--target",
425            target
426        );
427        if !rustversion::cfg!(nightly) {
428            cmd.env("RUSTC_BOOTSTRAP", "1");
429        }
430        let raw = cmd.read()?;
431        Ok((serde_json::from_str(&raw).map_err(Error::new)?, raw))
432    }
433
434    fn all_target_specs_json() -> Result<(AllTargetSpecs, String)> {
435        let mut cmd = cmd!("rustc", "-Z", "unstable-options", "--print", "all-target-specs-json");
436        if !rustversion::cfg!(nightly) {
437            cmd.env("RUSTC_BOOTSTRAP", "1");
438        }
439        let raw = cmd.read()?;
440        Ok((serde_json::from_str(&raw).map_err(Error::new)?, raw))
441    }
442
443    // Skip pre-1.91 because target-pointer-width change
444    #[rustversion::attr(before(1.91), ignore)]
445    #[test]
446    #[cfg_attr(miri, ignore)] // Miri doesn't support std::process::Command: https://github.com/rust-lang/miri/issues/3374
447    fn parse_target_spec_json() {
448        // builtin targets
449        for target in cmd!("rustc", "--print", "target-list").read().unwrap().lines() {
450            eprintln!("target={target}:");
451            let (parsed, raw) = target_spec_json(target).unwrap();
452            let deserialized = serde_json::to_string(&parsed).unwrap();
453            assert_eq!(
454                serde_json::from_str::<serde_json::Value>(&raw).unwrap(),
455                serde_json::from_str::<serde_json::Value>(&deserialized).unwrap()
456            );
457        }
458        eprintln!("all-targets:");
459        let (parsed, raw) = all_target_specs_json().unwrap();
460        let deserialized = serde_json::to_string(&parsed).unwrap();
461        assert_eq!(
462            serde_json::from_str::<serde_json::Value>(&raw).unwrap(),
463            serde_json::from_str::<serde_json::Value>(&deserialized).unwrap()
464        );
465        // TODO: custom targets
466        // for spec_path in fs::read_dir(fixtures_path().join("target-specs"))
467        //     .unwrap()
468        //     .map(|e| e.unwrap().path())
469        // {
470        // }
471    }
472}