This commit is contained in:
nora 2024-02-06 20:20:51 +01:00
parent 33748193aa
commit a58ec204c5
5 changed files with 195 additions and 41 deletions

View file

@ -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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>()
.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::<Vec<_>>();
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<TargetMaintainerEntry>,
struct TargetInfoTable {
target: Vec<TargetInfoPattern>,
}
#[derive(Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct TargetMaintainerEntry {
struct TargetInfoPattern {
pattern: String,
#[serde(default)]
maintainers: Vec<String>,
tier: Option<u8>,
requirements: Option<String>,
testing: Option<String>,
building_the_target: Option<String>,
cross_compilation: Option<String>,
building_rust_programs: Option<String>,
maintainers: Vec<String>,
}
fn target_info(target: &str) -> TargetDocs {
let file = include_str!("../target_info.toml");
let table = toml::from_str::<TargetMaintainerTable>(file).unwrap();
struct TargetPatternEntry {
info: TargetInfoPattern,
used: bool,
}
fn load_target_info_patterns() -> Vec<TargetPatternEntry> {
let file = include_str!("../target_info.toml");
let table = toml::from_str::<TargetInfoTable>(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<T>(
fn set_once<T: Clone>(
target: &str,
pattern_value: Option<T>,
pattern_value: &Option<T>,
to_insert: &mut Option<T>,
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()
}