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