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