diff --git a/Cargo.lock b/Cargo.lock index 27d10c4..a2414c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,37 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "equivalent" version = "1.0.1" @@ -54,6 +85,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "serde" version = "1.0.196" @@ -99,6 +150,7 @@ name = "target-docs" version = "0.1.0" dependencies = [ "glob-match", + "rayon", "serde", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index 3c76f30..3395695 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] glob-match = "0.2.1" +rayon = "1.8.1" serde = { version = "1.0.196", features = ["derive"] } toml = "0.8.10" diff --git a/README.md b/README.md index 75116fb..d653042 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,4 @@ By adding yet another preprocessing step, we can solve all these problems. - Error when there is documentation that is not needed anymore, for example a removed target - Still keep the nice and easy-to-organize glob structure in the source - Use a unified structure for all the pages +- This also allows us to put more dynamic values into the docs. For example, I put `--print cfg` there, isn't that pretty!? diff --git a/src/main.rs b/src/main.rs index b440d1a..25de99d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,10 @@ -use std::io; +use std::{ + io, + path::{Path, PathBuf}, + process::Command, +}; + +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; struct TargetDocs { name: String, @@ -11,7 +17,50 @@ struct TargetDocs { tier: u8, } -fn render_target_md(target: &TargetDocs) -> String { +fn main() { + let rustc = + PathBuf::from(std::env::var("RUSTC").expect("must pass RUSTC env var pointing to rustc")); + + let targets = rustc_stdout(&rustc, &["--print", "target-list"]); + let targets = targets.lines().collect::>(); + + match std::fs::create_dir("targets/src") { + Ok(()) => {} + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} + e @ _ => e.unwrap(), + } + + let mut info_patterns = load_target_info_patterns(); + + eprintln!("Collecting rustc information"); + let rustc_infos = targets + .par_iter() + .map(|target| rustc_target_info(&rustc, target)) + .collect::>(); + + eprintln!("Rendering targets"); + for (target, rustc_info) in std::iter::zip(&targets, rustc_infos) { + let info = target_info(&mut info_patterns, target); + let doc = render_target_md(&info, &rustc_info); + + std::fs::write(format!("targets/src/{target}.md"), doc).unwrap(); + } + + for target_pattern in info_patterns { + if !target_pattern.used { + panic!( + "target pattern `{}` was never used", + target_pattern.info.pattern + ); + } + } + + render_static(&targets); + + eprintln!("Finished generating target docs"); +} + +fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> String { let mut doc = format!("# {}\n**Tier: {}**", target.name, target.tier); let maintainers_str = if target.maintainers.is_empty() { @@ -55,24 +104,20 @@ fn render_target_md(target: &TargetDocs) -> String { section(&target.cross_compilation, "Cross Compilation"); section(&target.building_rust_programs, "Building Rust Programs"); + let cfg_text = rustc_info + .target_cfgs + .iter() + .map(|(key, value)| format!("- `{key}` = `{value}`")) + .collect::>() + .join("\n"); + let cfg_text = + format!("This target defines the following target-specific cfg values:\n{cfg_text}\n"); + section(&Some(cfg_text), "cfg"); + doc } -fn main() { - let targets = include_str!("../targets.txt").lines().collect::>(); - - match std::fs::create_dir("targets/src") { - Ok(()) => {} - Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} - e @ _ => e.unwrap(), - } - - for target in &targets { - let doc = render_target_md(&target_info(target)); - - std::fs::write(format!("targets/src/{target}.md"), doc).unwrap(); - } - +fn render_static(targets: &[&str]) { std::fs::write( format!("targets/src/SUMMARY.md"), format!( @@ -89,39 +134,56 @@ fn main() { ), ) .unwrap(); - std::fs::write("targets/src/information.md", "\ + std::fs::write( + "targets/src/information.md", + "\ # platform support generated This is an experiment of what target tier documentation could look like. See https://github.com/Nilstrieb/target-tier-docs-experiment for the source. - ").unwrap(); - println!("generated some target docs :3"); + ", + ) + .unwrap(); } #[derive(Debug, serde::Deserialize)] #[serde(deny_unknown_fields)] -struct TargetMaintainerTable { - target: Vec, +struct TargetInfoTable { + target: Vec, } #[derive(Debug, serde::Deserialize)] #[serde(deny_unknown_fields)] -struct TargetMaintainerEntry { +struct TargetInfoPattern { pattern: String, + #[serde(default)] + maintainers: Vec, tier: Option, requirements: Option, testing: Option, building_the_target: Option, cross_compilation: Option, building_rust_programs: Option, - maintainers: Vec, } -fn target_info(target: &str) -> TargetDocs { - let file = include_str!("../target_info.toml"); - let table = toml::from_str::(file).unwrap(); +struct TargetPatternEntry { + info: TargetInfoPattern, + used: bool, +} +fn load_target_info_patterns() -> Vec { + let file = include_str!("../target_info.toml"); + let table = toml::from_str::(file).unwrap(); + + table + .target + .into_iter() + .map(|info| TargetPatternEntry { info, used: false }) + .collect() +} + +fn target_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> TargetDocs { let mut tier = None; let mut maintainers = Vec::new(); let mut requirements = None; @@ -130,13 +192,16 @@ fn target_info(target: &str) -> TargetDocs { let mut cross_compilation = None; let mut building_rust_programs = None; - for target_pattern in table.target { - if glob_match::glob_match(&target_pattern.pattern, target) { + for target_pattern in info_patterns { + if glob_match::glob_match(&target_pattern.info.pattern, target) { + target_pattern.used = true; + let target_pattern = &target_pattern.info; + maintainers.extend_from_slice(&target_pattern.maintainers); - fn set_once( + fn set_once( target: &str, - pattern_value: Option, + pattern_value: &Option, to_insert: &mut Option, name: &str, ) { @@ -144,23 +209,21 @@ fn target_info(target: &str) -> TargetDocs { if to_insert.is_some() { panic!("target {target} inherits a {name} from multiple patterns, create a more specific pattern and add it there"); } - *to_insert = Some(pattern_value); + *to_insert = Some(pattern_value.clone()); } } #[rustfmt::skip] { - set_once(target, target_pattern.tier, &mut tier, "tier"); - set_once(target, target_pattern.requirements, &mut requirements, "requirements"); - set_once(target, target_pattern.testing, &mut testing, "testing"); - set_once(target, target_pattern.building_the_target, &mut building_the_target, "building_the_target"); - set_once(target, target_pattern.cross_compilation, &mut cross_compilation, "cross_compilation"); - set_once(target, target_pattern.building_rust_programs, &mut building_rust_programs, "building_rust_programs"); + set_once(target, &target_pattern.tier, &mut tier, "tier"); + set_once(target, &target_pattern.requirements, &mut requirements, "requirements"); + set_once(target, &target_pattern.testing, &mut testing, "testing"); + set_once(target, &target_pattern.building_the_target, &mut building_the_target, "building_the_target"); + set_once(target, &target_pattern.cross_compilation, &mut cross_compilation, "cross_compilation"); + set_once(target, &target_pattern.building_rust_programs, &mut building_rust_programs, "building_rust_programs"); }; } } - // we should give errors for unused patterns. - TargetDocs { name: target.to_owned(), maintainers, @@ -173,3 +236,38 @@ fn target_info(target: &str) -> TargetDocs { tier: tier.unwrap_or(0), } } + +struct RustcTargetInfo { + target_cfgs: Vec<(String, String)>, +} + +fn rustc_target_info(rustc: &Path, target: &str) -> RustcTargetInfo { + let cfgs = rustc_stdout(rustc, &["--print", "cfg", "--target", target]); + let target_cfgs = cfgs + .lines() + .filter_map(|line| { + if line.starts_with("target_") { + let Some((key, value)) = line.split_once("=") else { + // For example `unix` + return None; + }; + Some((key.to_owned(), value.to_owned())) + } else { + None + } + }) + .collect(); + RustcTargetInfo { target_cfgs } +} + +fn rustc_stdout(rustc: &Path, args: &[&str]) -> String { + let output = Command::new(rustc).args(args).output().unwrap(); + if !output.status.success() { + panic!( + "rustc failed: {}, {}", + output.status, + String::from_utf8(output.stderr).unwrap_or_default() + ) + } + String::from_utf8(output.stdout).unwrap() +} diff --git a/target_info.toml b/target_info.toml index e519266..5458835 100644 --- a/target_info.toml +++ b/target_info.toml @@ -1,3 +1,5 @@ +# An actual implementation would probably split this over several files, however it is most conventient. + [[target]] pattern = "*-apple-tvos" tier = 2 @@ -46,4 +48,4 @@ Binary format of this platform is XCOFF. Archive file format is 'AIX big format' """ testing = """ This target supports running test suites natively, but it's not available to cross-compile and execute in emulator. -""" \ No newline at end of file +"""