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