From 720b23bd8ed649fb8e6aaab09ee1d25fb2f03ee4 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 16 Mar 2024 11:08:34 +0100 Subject: [PATCH] sync --- Cargo.lock | 64 ++++----------------- Cargo.toml | 5 +- README.md | 69 ++++++++++++++++------- src/main.rs | 137 +++++++++++++++++++++++---------------------- src/parse.rs | 118 +++++--------------------------------- src/parse/tests.rs | 80 ++++++++++++++++++++++++++ src/render.rs | 71 +++++++---------------- 7 files changed, 249 insertions(+), 295 deletions(-) create mode 100644 src/parse/tests.rs diff --git a/Cargo.lock b/Cargo.lock index f328054..6a640be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,37 +2,6 @@ # 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" @@ -107,26 +76,6 @@ 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 = "ryu" version = "1.0.16" @@ -153,6 +102,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.31" @@ -183,8 +143,8 @@ version = "0.1.0" dependencies = [ "eyre", "glob-match", - "rayon", "serde", + "serde_json", "serde_yaml", ] diff --git a/Cargo.toml b/Cargo.toml index 3228e64..7874171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,13 @@ name = "target-docs" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] eyre = "0.6.12" glob-match = "0.2.1" -rayon = "1.8.1" -serde = { version = "1.0.196", features = ["derive"] } +serde = { version = "1.0.185", features = ["derive"] } +serde_json = "1.0.114" serde_yaml = "0.9.31" diff --git a/README.md b/README.md index 79220b8..6059944 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,57 @@ -# target tier docs experiment +# target-docs -Experiment with automatically generating target tier docs. +This tool generates target documentation for all targets in the rustc book. -**View the deployment on ** +To achieve this, it uses a list of input markdown files provided in `src/doc/rustc/target_infos`. These files follow a strict format. +Every file covers a glob pattern of targets according to its file name. -## Problems +For every rustc target, we iterate through all the target infos and find matching globs. +When a glob matches, it extracts the h2 markdown sections and saves them for the target. -Currenly, the [target tier docs](https://doc.rust-lang.org/rustc/platform-support.html) are hard to navigate. -If you want to find information about a specific target, you first need to do some glob-search yourself and then also hope -that the target actually exists. This is super annoying (`:(`). Additionally, some targets are completely missing and there -is no reason to believe that the documentation won't suddenly start being out of date. -Pages are also inconsistent about which sections exist and which ones don't. +In the end, a page is generated for every target using these sections. +Sections that are not provided are stubbed out. Currently, the sections are -## Solution +- Overview +- Requirements +- Testing +- Building the target +- Cross compilation +- Building Rust programs -Enter: adding yet another preprocessing step. +In addition to the markdown sections, we also have extra data about the targets. +This is achieved through YAML frontmatter. -By adding yet another preprocessing step, we can solve all these problems. -- Have a *dedicated* page for *every single* target including information about maintainers etc. - This makes it super easy to find things when there are problems. -- Ensure that no target is completely undocumented, at least having a stub page pointing out the undocumentedness -- 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!? +The frontmatter follows the following format: + +```yaml +tier: "1" +maintainers: ["@someone"] +metadata: + - target: "i686-pc-windows-gnu" + notes: "32-bit MinGW (Windows 7+)" + std: true + host: true + footnotes: + - name: "x86_32-floats-return-ABI" + content: | + Due to limitations of the C ABI, floating-point support on `i686` targets is non-compliant: + floating-point return values are passed via an x87 register, so NaN payload bits can be lost. + See [issue #114479][https://github.com/rust-lang/rust/issues/114479]. + - name: "windows-support" + content: "Only Windows 10 currently undergoes automated testing. Earlier versions of Windows rely on testing and support from the community." +``` + +The top level keys are: + +- `tier` (optional): `1`, `2` or `3` +- `maintainers` (optional): list of strings + +There is also `metadata`, which is specific to every single target and not just a target "group" (the glob). + +`metadata` has the following properties: + +- `target`: the target name +- `notes`: a string containing a short description of the target for the table +- `std`: `true`, `false`, `unknown`, whether the target has `std` +- `host`: `true`, `false`, `unknown`, whether the target has host tools +- `footnotes` (optional): a list of footnotes, where every footnote has a `name` and `content`. These are used in the table. diff --git a/src/main.rs b/src/main.rs index a931dc4..e03d039 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,33 +2,27 @@ mod parse; mod render; use std::{ - io, path::{Path, PathBuf}, process::Command, }; use eyre::{bail, Context, OptionExt, Result}; use parse::{Footnote, ParsedTargetInfoFile, Tier, TriStateBool}; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use serde::Deserialize; -/// Information about a target obtained from `target_info.toml``. +/// Information about a target obtained from the target_info markdown file. struct TargetDocs { name: String, maintainers: Vec, sections: Vec<(String, String)>, - tier: Option, - // TODO: Make this mandatory. - metadata: Option, -} - -/// Metadata for the table -struct TargetMetadata { - notes: String, - std: TriStateBool, - host: TriStateBool, - footnotes: Vec, + footnotes: Vec, } +/// All the sections that we want every doc page to have. +/// It may make sense to relax this into two kinds of sections, "required" sections +/// and "optional" sections, where required sections will get stubbed out when not found +/// while optional sections will just not exist when not found. +// IMPORTANT: This is also documented in the README, keep it in sync. const SECTIONS: &[&str] = &[ "Overview", "Requirements", @@ -38,51 +32,34 @@ const SECTIONS: &[&str] = &[ "Building Rust programs", ]; -fn is_in_rust_lang_rust() -> bool { - std::env::var("RUST_LANG_RUST") == Ok("1".to_owned()) -} - fn main() -> Result<()> { let args = std::env::args().collect::>(); let input_dir = args .get(1) - .ok_or_eyre("first argument must be path to directory containing source md files")?; + .ok_or_eyre("first argument must be path to target_infos directory containing target source md files (src/doc/rustc/target_infos/)")?; let output_src = args .get(2) - .ok_or_eyre("second argument must be path to `src` output directory")?; + .ok_or_eyre("second argument must be path to `src` output directory (src/doc/rustc/src)")?; let rustc = PathBuf::from(std::env::var("RUSTC").expect("must pass RUSTC env var pointing to rustc")); + let check_only = std::env::var("TARGET_CHECK_ONLY") == Ok("1".to_owned()); let targets = rustc_stdout(&rustc, &["--print", "target-list"]); let targets = targets.lines().collect::>(); - if !is_in_rust_lang_rust() { - match std::fs::create_dir("targets/src") { - Ok(()) => {} - Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} - e @ _ => e.wrap_err("failed creating src dir")?, - } - } - let mut info_patterns = parse::load_target_infos(Path::new(input_dir)) .wrap_err("failed loading target_info")? .into_iter() .map(|info| { let metadata_used = vec![false; info.metadata.len()]; - TargetPatternEntry { - info, - used: false, - metadata_used, - } + TargetPatternEntry { info, used: false, footnotes_used: metadata_used } }) .collect::>(); eprintln!("Collecting rustc information"); - let rustc_infos = targets - .par_iter() - .map(|target| rustc_target_info(&rustc, target)) - .collect::>(); + let rustc_infos = + targets.iter().map(|target| rustc_target_info(&rustc, target)).collect::>(); let targets = targets .into_iter() @@ -90,42 +67,39 @@ fn main() -> Result<()> { .zip(rustc_infos) .collect::>(); - eprintln!("Rendering targets"); + eprintln!("Rendering targets check_only={check_only}"); + let targets_dir = Path::new(output_src).join("platform-support").join("targets"); + if !check_only { + std::fs::create_dir_all(&targets_dir).wrap_err("creating platform-support/targets dir")?; + } for (info, rustc_info) in &targets { let doc = render::render_target_md(info, rustc_info); - std::fs::write( - Path::new(output_src) - .join("platform-support") - .join("targets") - .join(format!("{}.md", info.name)), - doc, - ) - .wrap_err("writing target file")?; + if !check_only { + std::fs::write(targets_dir.join(format!("{}.md", info.name)), doc) + .wrap_err("writing target file")?; + } } for target_pattern in info_patterns { if !target_pattern.used { - bail!( - "target pattern `{}` was never used", - target_pattern.info.pattern - ); + bail!("target pattern `{}` was never used", target_pattern.info.pattern); } for (used, meta) in - std::iter::zip(target_pattern.metadata_used, target_pattern.info.metadata) + std::iter::zip(target_pattern.footnotes_used, target_pattern.info.metadata) { if !used { bail!( - "in target pattern `{}`, the metadata pattern `{}` was never used", + "in target pattern `{}`, the footnotes for target `{}` were never used", target_pattern.info.pattern, - meta.pattern + meta.target ); } } } - render::render_static(Path::new(output_src), &targets)?; + render::render_static(check_only, Path::new(output_src), &targets)?; eprintln!("Finished generating target docs"); Ok(()) @@ -134,7 +108,7 @@ fn main() -> Result<()> { struct TargetPatternEntry { info: ParsedTargetInfoFile, used: bool, - metadata_used: Vec, + footnotes_used: Vec, } fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> TargetDocs { @@ -143,6 +117,7 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta let mut sections = Vec::new(); let mut metadata = None; + let mut footnotes = Vec::new(); for target_pattern_entry in info_patterns { if glob_match::glob_match(&target_pattern_entry.info.pattern, target) { @@ -153,21 +128,34 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta if let Some(pattern_value) = &target_pattern.tier { if tier.is_some() { - panic!("target {target} inherits a tier from multiple patterns, create a more specific pattern and add it there"); + panic!( + "target {target} inherits a tier from multiple patterns, create a more specific pattern and add it there" + ); } tier = Some(pattern_value.clone()); } for (section_name, content) in &target_pattern.sections { if sections.iter().any(|(name, _)| name == section_name) { - panic!("target {target} inherits the section {section_name} from multiple patterns, create a more specific pattern and add it there"); + panic!( + "target {target} inherits the section {section_name} from multiple patterns, create a more specific pattern and add it there" + ); } sections.push((section_name.clone(), content.clone())); } + if let Some(target_footnotes) = target_pattern.footnotes.get(target) { + target_pattern_entry.footnotes_used[i] = true; + + if !footnotes.is_empty() { + panic!("target {target} is assigned metadata from more than one pattern"); + } + footnotes = target_footnotes.clone(); + } + for (i, metadata_pattern) in target_pattern.metadata.iter().enumerate() { - if glob_match::glob_match(&metadata_pattern.pattern, target) { - target_pattern_entry.metadata_used[i] = true; + if metadata_pattern.target == target { + target_pattern_entry.footnotes_used[i] = true; if metadata.is_some() { panic!("target {target} is assigned metadata from more than one pattern"); } @@ -182,18 +170,21 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta } } - TargetDocs { - name: target.to_owned(), - maintainers, - tier, - sections, - metadata, - } + TargetDocs { name: target.to_owned(), maintainers, sections, footnotes } } /// Information about a target obtained from rustc. struct RustcTargetInfo { target_cfgs: Vec<(String, String)>, + metadata: RustcTargetMetadata, +} + +#[derive(Deserialize)] +struct RustcTargetMetadata { + description: Option, + tier: Option, + host_tools: Option, + std: Option, } /// Get information about a target from rustc. @@ -213,7 +204,19 @@ fn rustc_target_info(rustc: &Path, target: &str) -> RustcTargetInfo { } }) .collect(); - RustcTargetInfo { target_cfgs } + + #[derive(Deserialize)] + struct TargetJson { + metadata: RustcTargetMetadata, + } + + let json_spec = rustc_stdout( + rustc, + &["-Zunstable-options", "--print", "target-spec-json", "--target", target], + ); + let spec = serde_json::from_str::(&json_spec); + + RustcTargetInfo { target_cfgs, metadata: spec.metadata } } fn rustc_stdout(rustc: &Path, args: &[&str]) -> String { diff --git a/src/parse.rs b/src/parse.rs index e1be2bc..00fc64a 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -2,7 +2,7 @@ use eyre::{bail, OptionExt, Result, WrapErr}; use serde::Deserialize; -use std::{fs::DirEntry, path::Path}; +use std::{collections::HashMap, fs::DirEntry, path::Path}; #[derive(Debug, PartialEq, Clone, Deserialize)] pub enum Tier { @@ -20,9 +20,10 @@ pub struct ParsedTargetInfoFile { pub tier: Option, pub maintainers: Vec, pub sections: Vec<(String, String)>, - pub metadata: Vec, + pub footnotes: HashMap>, } +// IMPORTANT: This is also documented in the README, keep it in sync. #[derive(Deserialize)] #[serde(deny_unknown_fields)] struct Frontmatter { @@ -30,27 +31,19 @@ struct Frontmatter { #[serde(default)] maintainers: Vec, #[serde(default)] - metadata: Vec, + footnotes: HashMap>, } +// IMPORTANT: This is also documented in the README, keep it in sync. #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] -pub struct ParsedTargetMetadata { - pub pattern: String, - pub notes: String, - pub std: TriStateBool, - pub host: TriStateBool, +pub struct TargetFootnotes { + pub target: String, #[serde(default)] - pub footnotes: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Footnote { - pub name: String, - pub content: String, + pub footnotes: Vec, } +// IMPORTANT: This is also documented in the README, keep it in sync. #[derive(Debug, PartialEq, Clone, Copy, Deserialize)] #[serde(rename_all = "snake_case")] pub enum TriStateBool { @@ -89,9 +82,7 @@ fn load_single_target_info(entry: &DirEntry) -> Result { fn parse_file(name: &str, content: &str) -> Result { let mut frontmatter_splitter = content.split("---\n"); - let frontmatter = frontmatter_splitter - .nth(1) - .ok_or_eyre("missing frontmatter")?; + let frontmatter = frontmatter_splitter.nth(1).ok_or_eyre("missing frontmatter")?; let frontmatter_line_count = frontmatter.lines().count() + 2; // 2 from --- @@ -149,99 +140,16 @@ fn parse_file(name: &str, content: &str) -> Result { } } - sections - .iter_mut() - .for_each(|section| section.1 = section.1.trim().to_owned()); + sections.iter_mut().for_each(|section| section.1 = section.1.trim().to_owned()); Ok(ParsedTargetInfoFile { pattern: name.to_owned(), maintainers: frontmatter.maintainers, tier: frontmatter.tier, sections, - metadata: frontmatter.metadata, + footnotes: frontmatter.footnotes, }) } #[cfg(test)] -mod tests { - use crate::parse::Tier; - - #[test] - fn no_frontmatter() { - let name = "archlinux-unknown-linux-gnu.md"; // arch linux is an arch, right? - let content = ""; - assert!(super::parse_file(name, content).is_err()); - } - - #[test] - fn invalid_section() { - let name = "6502-nintendo-nes.md"; - let content = " ---- ---- - -## Not A Real Section -"; - - assert!(super::parse_file(name, content).is_err()); - } - - #[test] - fn wrong_header() { - let name = "x86_64-known-linux-gnu.md"; - let content = " ---- ---- - -# x86_64-known-linux-gnu -"; - - assert!(super::parse_file(name, content).is_err()); - } - - #[test] - fn parse_correctly() { - let name = "cat-unknown-linux-gnu.md"; - let content = r#" ---- -tier: "1" # first-class cats -maintainers: ["who maintains the cat?"] ---- -## Requirements - -This target mostly just meows and doesn't do much. - -## Testing - -You can pet the cat and it might respond positively. - -## Cross compilation - -If you're on a dog system, there might be conflicts with the cat, be careful. -But it should be possible. - "#; - - let info = super::parse_file(name, content).unwrap(); - - assert_eq!(info.maintainers, vec!["who maintains the cat?"]); - assert_eq!(info.pattern, name); - assert_eq!(info.tier, Some(Tier::One)); - assert_eq!( - info.sections, - vec![ - ( - "Requirements".to_owned(), - "This target mostly just meows and doesn't do much.".to_owned(), - ), - ( - "Testing".to_owned(), - "You can pet the cat and it might respond positively.".to_owned(), - ), - ( - "Cross compilation".to_owned(), - "If you're on a dog system, there might be conflicts with the cat, be careful.\nBut it should be possible.".to_owned(), - ), - ] - ); - } -} +mod tests; diff --git a/src/parse/tests.rs b/src/parse/tests.rs new file mode 100644 index 0000000..cdaaa1f --- /dev/null +++ b/src/parse/tests.rs @@ -0,0 +1,80 @@ +use crate::parse::Tier; + +#[test] +fn no_frontmatter() { + let name = "archlinux-unknown-linux-gnu.md"; // arch linux is an arch, right? + let content = ""; + assert!(super::parse_file(name, content).is_err()); +} + +#[test] +fn invalid_section() { + let name = "6502-nintendo-nes.md"; + let content = " +--- +--- + +## Not A Real Section +"; + + assert!(super::parse_file(name, content).is_err()); +} + +#[test] +fn wrong_header() { + let name = "x86_64-known-linux-gnu.md"; + let content = " +--- +--- + +# x86_64-known-linux-gnu +"; + + assert!(super::parse_file(name, content).is_err()); +} + +#[test] +fn parse_correctly() { + let name = "cat-unknown-linux-gnu.md"; + let content = r#" +--- +tier: "1" # first-class cats +maintainers: ["who maintains the cat?"] +--- +## Requirements + +This target mostly just meows and doesn't do much. + +## Testing + +You can pet the cat and it might respond positively. + +## Cross compilation + +If you're on a dog system, there might be conflicts with the cat, be careful. +But it should be possible. + "#; + + let info = super::parse_file(name, content).unwrap(); + + assert_eq!(info.maintainers, vec!["who maintains the cat?"]); + assert_eq!(info.pattern, name); + assert_eq!(info.tier, Some(Tier::One)); + assert_eq!( + info.sections, + vec![ + ( + "Requirements".to_owned(), + "This target mostly just meows and doesn't do much.".to_owned(), + ), + ( + "Testing".to_owned(), + "You can pet the cat and it might respond positively.".to_owned(), + ), + ( + "Cross compilation".to_owned(), + "If you're on a dog system, there might be conflicts with the cat, be careful.\nBut it should be possible.".to_owned(), + ), + ] + ); +} diff --git a/src/render.rs b/src/render.rs index 9bdcd72..c90a0f5 100644 --- a/src/render.rs +++ b/src/render.rs @@ -2,16 +2,13 @@ use eyre::{Context, OptionExt, Result}; use std::{fs, path::Path}; use crate::{ - is_in_rust_lang_rust, parse::{Footnote, Tier, TriStateBool}, RustcTargetInfo, TargetDocs, }; impl TargetDocs { fn has_host_tools(&self) -> bool { - self.metadata - .as_ref() - .map_or(false, |meta| meta.host == TriStateBool::True) + self.metadata.as_ref().map_or(false, |meta| meta.host == TriStateBool::True) } } @@ -64,10 +61,7 @@ pub fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> St section("Maintainers", &maintainers_content); for section_name in crate::SECTIONS { - let value = target - .sections - .iter() - .find(|(name, _)| name == section_name); + let value = target.sections.iter().find(|(name, _)| name == section_name); let section_content = match value { Some((_, value)) => value.clone(), @@ -112,7 +106,11 @@ fn replace_section(prev_content: &str, section_name: &str, replacement: &str) -> } /// Renders the non-target files like `SUMMARY.md` that depend on the target. -pub fn render_static(src_output: &Path, targets: &[(TargetDocs, RustcTargetInfo)]) -> Result<()> { +pub fn render_static( + check_only: bool, + src_output: &Path, + targets: &[(TargetDocs, RustcTargetInfo)], +) -> Result<()> { let targets_file = src_output.join("platform-support").join("targets.md"); let old_targets = fs::read_to_string(&targets_file).wrap_err("reading summary file")?; @@ -125,26 +123,8 @@ pub fn render_static(src_output: &Path, targets: &[(TargetDocs, RustcTargetInfo) let new_targets = replace_section(&old_targets, "TARGET", &target_list).wrap_err("replacing targets.md")?; - fs::write(targets_file, new_targets).wrap_err("writing targets.md")?; - - if !is_in_rust_lang_rust() { - fs::write( - "targets/src/information.md", - "\ - # platform support generated - - This is an experiment of what generated target tier documentation could look like. - - See for the source. - The README of the repo contains more information about the motivation and benefits. - - Targets of interest with information filled out are any tvos targets like [aarch64-apple-tvos](./aarch64-apple-tvos.md) - and [powerpc64-ibm-aix](./powerpc64-ibm-aix.md). - - But as you might notice, all targets are actually present with a stub :3. - ", - ) - .wrap_err("writing front page information about experiment")?; + if !check_only { + fs::write(targets_file, new_targets).wrap_err("writing targets.md")?; } let platform_support_main = src_output.join("platform-support.md"); @@ -152,8 +132,11 @@ pub fn render_static(src_output: &Path, targets: &[(TargetDocs, RustcTargetInfo) fs::read_to_string(&platform_support_main).wrap_err("reading platform-support.md")?; let platform_support_main_new = render_platform_support_tables(&platform_support_main_old, targets)?; - fs::write(platform_support_main, platform_support_main_new) - .wrap_err("writing platform-support.md")?; + + if !check_only { + fs::write(platform_support_main, platform_support_main_new) + .wrap_err("writing platform-support.md")?; + } Ok(()) } @@ -227,47 +210,35 @@ struct TierTable { include_host: bool, } -fn render_table<'a>(targets: &[(TargetDocs, RustcTargetInfo)], table: TierTable) -> Result { +fn render_table(targets: &[(TargetDocs, RustcTargetInfo)], table: TierTable) -> Result { let mut rows = Vec::new(); let mut all_footnotes = Vec::new(); - let targets = targets - .into_iter() - .filter(|target| (table.filter)(&target.0)); + let targets = targets.into_iter().filter(|target| (table.filter)(&target.0)); for (target, _) in targets { let meta = target.metadata.as_ref(); - let mut notes = meta - .map(|meta| meta.notes.as_str()) - .unwrap_or("unknown") - .to_owned(); + let mut notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown").to_owned(); if meta.map_or(false, |meta| !meta.footnotes.is_empty()) { let footnotes = &meta.unwrap().footnotes; all_footnotes.extend(footnotes); - let footnotes_str = footnotes - .iter() - .map(|footnote| footnote.reference()) - .collect::>() - .join(" "); + let footnotes_str = + footnotes.iter().map(|footnote| footnote.reference()).collect::>().join(" "); notes = format!("{notes} {footnotes_str}"); } let std = if table.include_std { - let std = meta - .map(|meta| render_table_tri_state_bool(meta.std)) - .unwrap_or("?"); + let std = meta.map(|meta| render_table_tri_state_bool(meta.std)).unwrap_or("?"); format!(" | {std}") } else { String::new() }; let host = if table.include_host { - let host = meta - .map(|meta| render_table_tri_state_bool(meta.host)) - .unwrap_or("?"); + let host = meta.map(|meta| render_table_tri_state_bool(meta.host)).unwrap_or("?"); format!(" | {host}") } else { String::new()