This commit is contained in:
nora 2024-03-16 13:55:22 +01:00
parent a7cd3d46f9
commit 135c9bbbb1
4 changed files with 112 additions and 66 deletions

View file

@ -11,12 +11,14 @@ use eyre::{bail, Context, OptionExt, Result};
use parse::ParsedTargetInfoFile; use parse::ParsedTargetInfoFile;
use serde::Deserialize; use serde::Deserialize;
/// Information about a target obtained from the target_info markdown file. /// Information about a target obtained from the markdown and rustc.
struct TargetDocs { struct TargetInfo {
name: String, name: String,
maintainers: Vec<String>, maintainers: Vec<String>,
sections: Vec<(String, String)>, sections: Vec<(String, String)>,
footnotes: Vec<String>, footnotes: Vec<String>,
target_cfgs: Vec<(String, String)>,
metadata: RustcTargetMetadata,
} }
/// All the sections that we want every doc page to have. /// All the sections that we want every doc page to have.
@ -56,29 +58,48 @@ fn main() -> Result<()> {
.wrap_err("failed loading target_info")? .wrap_err("failed loading target_info")?
.into_iter() .into_iter()
.map(|info| { .map(|info| {
let footnotes_used = let footnotes_used = info
info.footnotes.iter().map(|(target, _)| (target.clone(), false)).collect(); .footnotes
TargetPatternEntry { info, used: false, footnotes_used } .keys()
.map(|target| (target.clone(), false))
.collect();
TargetPatternEntry {
info,
used: false,
footnotes_used,
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
eprintln!("Collecting rustc information"); eprintln!("Collecting rustc information");
let rustc_infos = let rustc_infos = targets
targets.iter().map(|target| rustc_target_info(&rustc, target)).collect::<Vec<_>>(); .iter()
.map(|target| rustc_target_info(&rustc, target))
.collect::<Vec<_>>();
let targets = targets let targets = targets
.into_iter() .into_iter()
.map(|target| target_doc_info(&mut info_patterns, target)) .map(|target| target_doc_info(&mut info_patterns, target))
.zip(rustc_infos) .zip(rustc_infos)
.map(|(md, rustc)| TargetInfo {
name: md.name,
maintainers: md.maintainers,
sections: md.sections,
footnotes: md.footnotes,
target_cfgs: rustc.target_cfgs,
metadata: rustc.metadata,
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
eprintln!("Rendering targets check_only={check_only}"); eprintln!("Rendering targets check_only={check_only}");
let targets_dir = Path::new(output_src).join("platform-support").join("targets"); let targets_dir = Path::new(output_src)
.join("platform-support")
.join("targets");
if !check_only { if !check_only {
std::fs::create_dir_all(&targets_dir).wrap_err("creating platform-support/targets dir")?; std::fs::create_dir_all(&targets_dir).wrap_err("creating platform-support/targets dir")?;
} }
for (info, rustc_info) in &targets { for info in &targets {
let doc = render::render_target_md(info, rustc_info); let doc = render::render_target_md(info);
if !check_only { if !check_only {
std::fs::write(targets_dir.join(format!("{}.md", info.name)), doc) std::fs::write(targets_dir.join(format!("{}.md", info.name)), doc)
@ -88,7 +109,10 @@ fn main() -> Result<()> {
for target_pattern in info_patterns { for target_pattern in info_patterns {
if !target_pattern.used { 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 footnote_target in target_pattern.info.footnotes.keys() { for footnote_target in target_pattern.info.footnotes.keys() {
@ -115,7 +139,15 @@ struct TargetPatternEntry {
footnotes_used: HashMap<String, bool>, footnotes_used: HashMap<String, bool>,
} }
fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> TargetDocs { /// Information about a target obtained from the target_info markdown file.
struct TargetInfoMd {
name: String,
maintainers: Vec<String>,
sections: Vec<(String, String)>,
footnotes: Vec<String>,
}
fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> TargetInfoMd {
let mut maintainers = Vec::new(); let mut maintainers = Vec::new();
let mut sections = Vec::new(); let mut sections = Vec::new();
@ -128,7 +160,6 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta
maintainers.extend_from_slice(&target_pattern.maintainers); maintainers.extend_from_slice(&target_pattern.maintainers);
for (section_name, content) in &target_pattern.sections { for (section_name, content) in &target_pattern.sections {
if sections.iter().any(|(name, _)| name == section_name) { if sections.iter().any(|(name, _)| name == section_name) {
panic!( panic!(
@ -139,7 +170,9 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta
} }
if let Some(target_footnotes) = target_pattern.footnotes.get(target) { if let Some(target_footnotes) = target_pattern.footnotes.get(target) {
target_pattern_entry.footnotes_used.insert(target.to_owned(), true); target_pattern_entry
.footnotes_used
.insert(target.to_owned(), true);
if !footnotes.is_empty() { if !footnotes.is_empty() {
panic!("target {target} is assigned metadata from more than one pattern"); panic!("target {target} is assigned metadata from more than one pattern");
@ -149,7 +182,12 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta
} }
} }
TargetDocs { name: target.to_owned(), maintainers, sections, footnotes } TargetInfoMd {
name: target.to_owned(),
maintainers,
sections,
footnotes,
}
} }
/// Information about a target obtained from rustc. /// Information about a target obtained from rustc.
@ -173,7 +211,7 @@ fn rustc_target_info(rustc: &Path, target: &str) -> RustcTargetInfo {
.lines() .lines()
.filter_map(|line| { .filter_map(|line| {
if line.starts_with("target_") { if line.starts_with("target_") {
let Some((key, value)) = line.split_once("=") else { let Some((key, value)) = line.split_once('=') else {
// For example `unix` // For example `unix`
return None; return None;
}; };
@ -191,12 +229,21 @@ fn rustc_target_info(rustc: &Path, target: &str) -> RustcTargetInfo {
let json_spec = rustc_stdout( let json_spec = rustc_stdout(
rustc, rustc,
&["-Zunstable-options", "--print", "target-spec-json", "--target", target], &[
"-Zunstable-options",
"--print",
"target-spec-json",
"--target",
target,
],
); );
let spec = serde_json::from_str::<TargetJson>(&json_spec) let spec = serde_json::from_str::<TargetJson>(&json_spec)
.expect("parsing --print target-spec-json for metadata"); .expect("parsing --print target-spec-json for metadata");
RustcTargetInfo { target_cfgs, metadata: spec.metadata } RustcTargetInfo {
target_cfgs,
metadata: spec.metadata,
}
} }
fn rustc_stdout(rustc: &Path, args: &[&str]) -> String { fn rustc_stdout(rustc: &Path, args: &[&str]) -> String {

View file

@ -70,7 +70,9 @@ fn load_single_target_info(entry: &DirEntry) -> Result<ParsedTargetInfoFile> {
fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> { fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
let mut frontmatter_splitter = content.split("---\n"); 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 --- let frontmatter_line_count = frontmatter.lines().count() + 2; // 2 from ---
@ -86,7 +88,7 @@ fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
let number = frontmatter_line_count + idx + 1; // 1 because "line numbers" are off by 1 let number = frontmatter_line_count + idx + 1; // 1 because "line numbers" are off by 1
if line.starts_with("```") { if line.starts_with("```") {
in_codeblock ^= true; // toggle in_codeblock ^= true; // toggle
} else if line.starts_with("#") { } else if line.starts_with('#') {
if in_codeblock { if in_codeblock {
match sections.last_mut() { match sections.last_mut() {
Some((_, content)) => { Some((_, content)) => {
@ -121,7 +123,9 @@ fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
} }
} }
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 { Ok(ParsedTargetInfoFile {
pattern: name.to_owned(), pattern: name.to_owned(),

View file

@ -1,5 +1,3 @@
use crate::parse::Tier;
#[test] #[test]
fn no_frontmatter() { fn no_frontmatter() {
let name = "archlinux-unknown-linux-gnu.md"; // arch linux is an arch, right? let name = "archlinux-unknown-linux-gnu.md"; // arch linux is an arch, right?
@ -38,7 +36,6 @@ fn parse_correctly() {
let name = "cat-unknown-linux-gnu.md"; let name = "cat-unknown-linux-gnu.md";
let content = r#" let content = r#"
--- ---
tier: "1" # first-class cats
maintainers: ["who maintains the cat?"] maintainers: ["who maintains the cat?"]
--- ---
## Requirements ## Requirements
@ -59,7 +56,6 @@ But it should be possible.
assert_eq!(info.maintainers, vec!["who maintains the cat?"]); assert_eq!(info.maintainers, vec!["who maintains the cat?"]);
assert_eq!(info.pattern, name); assert_eq!(info.pattern, name);
assert_eq!(info.tier, Some(Tier::One));
assert_eq!( assert_eq!(
info.sections, info.sections,
vec![ vec![

View file

@ -1,29 +1,27 @@
use eyre::{Context, OptionExt, Result}; use eyre::{Context, OptionExt, Result};
use std::{fs, path::Path}; use std::{fs, path::Path};
use crate::{RustcTargetInfo, TargetDocs}; use crate::TargetInfo;
/// Renders a single target markdown file from the information obtained. /// Renders a single target markdown file from the information obtained.
pub fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> String { pub fn render_target_md(target: &TargetInfo) -> String {
let render_header_option_bool = |bool| { let render_header_option_bool = |bool| match bool {
match bool { Some(true) => "Yes",
Some(true) => "Yes", Some(false) => "No",
Some(false) => "No", None => "?",
None => "?",
}
}; };
let mut doc = format!( let mut doc = format!(
"# {}\n\n**Tier: {}**\n\n**std: {}**\n\n**host tools: {}**\n\n", "# {}\n\n**Tier: {}**\n\n**std: {}**\n\n**host tools: {}**\n\n",
target.name, target.name,
match rustc_info.metadata.tier { match target.metadata.tier {
Some(1) => "1", Some(1) => "1",
Some(2) => "2", Some(2) => "2",
Some(3) => "3", Some(3) => "3",
_ => "UNKNOWN", _ => "UNKNOWN",
}, },
render_header_option_bool(rustc_info.metadata.std), render_header_option_bool(target.metadata.std),
render_header_option_bool(rustc_info.metadata.host_tools), render_header_option_bool(target.metadata.host_tools),
); );
let mut section = |name: &str, content: &str| { let mut section = |name: &str, content: &str| {
@ -43,7 +41,7 @@ pub fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> St
.maintainers .maintainers
.iter() .iter()
.map(|maintainer| { .map(|maintainer| {
let maintainer = if maintainer.starts_with('@') && !maintainer.contains(" ") { let maintainer = if maintainer.starts_with('@') && !maintainer.contains(' ') {
format!( format!(
"[@{0}](https://github.com/{0})", "[@{0}](https://github.com/{0})",
maintainer.strip_prefix("@").unwrap() maintainer.strip_prefix("@").unwrap()
@ -62,16 +60,19 @@ pub fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> St
section("Maintainers", &maintainers_content); section("Maintainers", &maintainers_content);
for section_name in crate::SECTIONS { 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 { let section_content = match value {
Some((_, value)) => value.clone(), Some((_, value)) => value.clone(),
None => "Unknown.".to_owned(), None => "Unknown.".to_owned(),
}; };
section(&section_name, &section_content); section(section_name, &section_content);
} }
let cfg_text = rustc_info let cfg_text = target
.target_cfgs .target_cfgs
.iter() .iter()
.map(|(key, value)| format!("- `{key}` = `{value}`")) .map(|(key, value)| format!("- `{key}` = `{value}`"))
@ -105,17 +106,13 @@ fn replace_section(prev_content: &str, section_name: &str, replacement: &str) ->
} }
/// Renders the non-target files like `SUMMARY.md` that depend on the target. /// Renders the non-target files like `SUMMARY.md` that depend on the target.
pub fn render_static( pub fn render_static(check_only: bool, src_output: &Path, targets: &[TargetInfo]) -> Result<()> {
check_only: bool,
src_output: &Path,
targets: &[(TargetDocs, RustcTargetInfo)],
) -> Result<()> {
let targets_file = src_output.join("platform-support").join("targets.md"); 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")?; let old_targets = fs::read_to_string(&targets_file).wrap_err("reading summary file")?;
let target_list = targets let target_list = targets
.iter() .iter()
.map(|(target, _)| format!("- [{0}](platform-support/targets/{0}.md)", target.name)) .map(|target| format!("- [{0}](platform-support/targets/{0}.md)", target.name))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
@ -140,9 +137,12 @@ pub fn render_static(
let summary = src_output.join("SUMMARY.md"); let summary = src_output.join("SUMMARY.md");
let summary_old = fs::read_to_string(&summary).wrap_err("reading SUMMARY.md")?; let summary_old = fs::read_to_string(&summary).wrap_err("reading SUMMARY.md")?;
// indent the list // indent the list
let summary_new = let summary_new = replace_section(
replace_section(&summary_old, "TARGET_LIST", &target_list.replace("- ", " - ")) &summary_old,
.wrap_err("replacig SUMMARY.md")?; "TARGET_LIST",
&target_list.replace("- ", " - "),
)
.wrap_err("replacig SUMMARY.md")?;
if !check_only { if !check_only {
fs::write(summary, summary_new).wrap_err("writing SUMAMRY.md")?; fs::write(summary, summary_new).wrap_err("writing SUMAMRY.md")?;
} }
@ -150,10 +150,13 @@ pub fn render_static(
Ok(()) Ok(())
} }
fn render_platform_support_tables( impl TargetInfo {
content: &str, fn has_host_tools(&self) -> bool {
targets: &[(TargetDocs, RustcTargetInfo)], self.metadata.host_tools.unwrap_or(false)
) -> Result<String> { }
}
fn render_platform_support_tables(content: &str, targets: &[TargetInfo]) -> Result<String> {
let replace_table = |content, name, tier_table| -> Result<String> { let replace_table = |content, name, tier_table| -> Result<String> {
let section_string = render_table(targets, tier_table)?; let section_string = render_table(targets, tier_table)?;
replace_section(content, name, &section_string).wrap_err("replacing platform support.md") replace_section(content, name, &section_string).wrap_err("replacing platform support.md")
@ -163,7 +166,7 @@ fn render_platform_support_tables(
content, content,
"TIER1HOST", "TIER1HOST",
TierTable { TierTable {
filter: |target| target.1.metadata.tier == Some(1), filter: |target| target.metadata.tier == Some(1),
include_host: false, include_host: false,
include_std: false, include_std: false,
}, },
@ -172,9 +175,7 @@ fn render_platform_support_tables(
&content, &content,
"TIER2HOST", "TIER2HOST",
TierTable { TierTable {
filter: |target| { filter: |target| target.metadata.tier == Some(2) && target.has_host_tools(),
target.1.metadata.tier == Some(2) && target.1.metadata.host_tools.unwrap_or(false)
},
include_host: false, include_host: false,
include_std: false, include_std: false,
}, },
@ -183,9 +184,7 @@ fn render_platform_support_tables(
&content, &content,
"TIER2", "TIER2",
TierTable { TierTable {
filter: |target| { filter: |target| target.metadata.tier == Some(2) && !target.has_host_tools(),
target.1.metadata.tier == Some(2) && !target.1.metadata.host_tools.unwrap_or(false)
},
include_host: false, include_host: false,
include_std: true, include_std: true,
}, },
@ -194,7 +193,7 @@ fn render_platform_support_tables(
&content, &content,
"TIER3", "TIER3",
TierTable { TierTable {
filter: |target| target.1.metadata.tier == Some(3), filter: |target| target.metadata.tier == Some(3),
include_host: true, include_host: true,
include_std: true, include_std: true,
}, },
@ -212,18 +211,18 @@ fn render_table_option_bool(bool: Option<bool>) -> &'static str {
} }
struct TierTable { struct TierTable {
filter: fn(&(TargetDocs, RustcTargetInfo)) -> bool, filter: fn(&TargetInfo) -> bool,
include_std: bool, include_std: bool,
include_host: bool, include_host: bool,
} }
fn render_table(targets: &[(TargetDocs, RustcTargetInfo)], table: TierTable) -> Result<String> { fn render_table(targets: &[TargetInfo], table: TierTable) -> Result<String> {
let mut rows = Vec::new(); let mut rows = Vec::new();
let targets = targets.into_iter().filter(|target| (table.filter)(&target)); let targets = targets.iter().filter(|target| (table.filter)(target));
for (target, rustc_info) in targets { for target in targets {
let meta = &rustc_info.metadata; let meta = &target.metadata;
let mut notes = meta.description.as_deref().unwrap_or("unknown").to_owned(); let mut notes = meta.description.as_deref().unwrap_or("unknown").to_owned();