This commit is contained in:
nora 2024-03-16 11:08:34 +01:00
parent a6884454c0
commit 720b23bd8e
7 changed files with 249 additions and 295 deletions

64
Cargo.lock generated
View file

@ -2,37 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -107,26 +76,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.16" version = "1.0.16"
@ -153,6 +102,17 @@ dependencies = [
"syn", "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]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.9.31" version = "0.9.31"
@ -183,8 +143,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"eyre", "eyre",
"glob-match", "glob-match",
"rayon",
"serde", "serde",
"serde_json",
"serde_yaml", "serde_yaml",
] ]

View file

@ -2,12 +2,13 @@
name = "target-docs" name = "target-docs"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
eyre = "0.6.12" eyre = "0.6.12"
glob-match = "0.2.1" glob-match = "0.2.1"
rayon = "1.8.1" serde = { version = "1.0.185", features = ["derive"] }
serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.114"
serde_yaml = "0.9.31" serde_yaml = "0.9.31"

View file

@ -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 <https://nilstrieb.github.io/target-tier-docs-experiment/>** 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. In the end, a page is generated for every target using these sections.
If you want to find information about a specific target, you first need to do some glob-search yourself and then also hope Sections that are not provided are stubbed out. Currently, the sections are
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.
## 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. The frontmatter follows the following format:
- 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. ```yaml
- Ensure that no target is completely undocumented, at least having a stub page pointing out the undocumentedness tier: "1"
- Error when there is documentation that is not needed anymore, for example a removed target maintainers: ["@someone"]
- Still keep the nice and easy-to-organize glob structure in the source metadata:
- Use a unified structure for all the pages - target: "i686-pc-windows-gnu"
- This also allows us to put more dynamic values into the docs. For example, I put `--print cfg` there, isn't that pretty!? 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.

View file

@ -2,33 +2,27 @@ mod parse;
mod render; mod render;
use std::{ use std::{
io,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
}; };
use eyre::{bail, Context, OptionExt, Result}; use eyre::{bail, Context, OptionExt, Result};
use parse::{Footnote, ParsedTargetInfoFile, Tier, TriStateBool}; 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 { struct TargetDocs {
name: String, name: String,
maintainers: Vec<String>, maintainers: Vec<String>,
sections: Vec<(String, String)>, sections: Vec<(String, String)>,
tier: Option<Tier>, footnotes: Vec<String>,
// TODO: Make this mandatory.
metadata: Option<TargetMetadata>,
}
/// Metadata for the table
struct TargetMetadata {
notes: String,
std: TriStateBool,
host: TriStateBool,
footnotes: Vec<Footnote>,
} }
/// 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] = &[ const SECTIONS: &[&str] = &[
"Overview", "Overview",
"Requirements", "Requirements",
@ -38,51 +32,34 @@ const SECTIONS: &[&str] = &[
"Building Rust programs", "Building Rust programs",
]; ];
fn is_in_rust_lang_rust() -> bool {
std::env::var("RUST_LANG_RUST") == Ok("1".to_owned())
}
fn main() -> Result<()> { fn main() -> Result<()> {
let args = std::env::args().collect::<Vec<_>>(); let args = std::env::args().collect::<Vec<_>>();
let input_dir = args let input_dir = args
.get(1) .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 let output_src = args
.get(2) .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 = let rustc =
PathBuf::from(std::env::var("RUSTC").expect("must pass RUSTC env var pointing to 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 = rustc_stdout(&rustc, &["--print", "target-list"]);
let targets = targets.lines().collect::<Vec<_>>(); let targets = targets.lines().collect::<Vec<_>>();
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)) let mut info_patterns = parse::load_target_infos(Path::new(input_dir))
.wrap_err("failed loading target_info")? .wrap_err("failed loading target_info")?
.into_iter() .into_iter()
.map(|info| { .map(|info| {
let metadata_used = vec![false; info.metadata.len()]; let metadata_used = vec![false; info.metadata.len()];
TargetPatternEntry { TargetPatternEntry { info, used: false, footnotes_used: metadata_used }
info,
used: false,
metadata_used,
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
eprintln!("Collecting rustc information"); eprintln!("Collecting rustc information");
let rustc_infos = targets let rustc_infos =
.par_iter() targets.iter().map(|target| rustc_target_info(&rustc, target)).collect::<Vec<_>>();
.map(|target| rustc_target_info(&rustc, target))
.collect::<Vec<_>>();
let targets = targets let targets = targets
.into_iter() .into_iter()
@ -90,42 +67,39 @@ fn main() -> Result<()> {
.zip(rustc_infos) .zip(rustc_infos)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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 { for (info, rustc_info) in &targets {
let doc = render::render_target_md(info, rustc_info); let doc = render::render_target_md(info, rustc_info);
std::fs::write( if !check_only {
Path::new(output_src) std::fs::write(targets_dir.join(format!("{}.md", info.name)), doc)
.join("platform-support") .wrap_err("writing target file")?;
.join("targets") }
.join(format!("{}.md", info.name)),
doc,
)
.wrap_err("writing target file")?;
} }
for target_pattern in info_patterns { for target_pattern in info_patterns {
if !target_pattern.used { if !target_pattern.used {
bail!( bail!("target pattern `{}` was never used", target_pattern.info.pattern);
"target pattern `{}` was never used",
target_pattern.info.pattern
);
} }
for (used, meta) in 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 { if !used {
bail!( bail!(
"in target pattern `{}`, the metadata pattern `{}` was never used", "in target pattern `{}`, the footnotes for target `{}` were never used",
target_pattern.info.pattern, 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"); eprintln!("Finished generating target docs");
Ok(()) Ok(())
@ -134,7 +108,7 @@ fn main() -> Result<()> {
struct TargetPatternEntry { struct TargetPatternEntry {
info: ParsedTargetInfoFile, info: ParsedTargetInfoFile,
used: bool, used: bool,
metadata_used: Vec<bool>, footnotes_used: Vec<bool>,
} }
fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> TargetDocs { 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 sections = Vec::new();
let mut metadata = None; let mut metadata = None;
let mut footnotes = Vec::new();
for target_pattern_entry in info_patterns { for target_pattern_entry in info_patterns {
if glob_match::glob_match(&target_pattern_entry.info.pattern, target) { 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 let Some(pattern_value) = &target_pattern.tier {
if tier.is_some() { 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()); tier = Some(pattern_value.clone());
} }
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!("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())); 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() { for (i, metadata_pattern) in target_pattern.metadata.iter().enumerate() {
if glob_match::glob_match(&metadata_pattern.pattern, target) { if metadata_pattern.target == target {
target_pattern_entry.metadata_used[i] = true; target_pattern_entry.footnotes_used[i] = true;
if metadata.is_some() { if metadata.is_some() {
panic!("target {target} is assigned metadata from more than one pattern"); 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 { TargetDocs { name: target.to_owned(), maintainers, sections, footnotes }
name: target.to_owned(),
maintainers,
tier,
sections,
metadata,
}
} }
/// Information about a target obtained from rustc. /// Information about a target obtained from rustc.
struct RustcTargetInfo { struct RustcTargetInfo {
target_cfgs: Vec<(String, String)>, target_cfgs: Vec<(String, String)>,
metadata: RustcTargetMetadata,
}
#[derive(Deserialize)]
struct RustcTargetMetadata {
description: Option<String>,
tier: Option<u8>,
host_tools: Option<bool>,
std: Option<bool>,
} }
/// Get information about a target from rustc. /// Get information about a target from rustc.
@ -213,7 +204,19 @@ fn rustc_target_info(rustc: &Path, target: &str) -> RustcTargetInfo {
} }
}) })
.collect(); .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::<TargetJson>(&json_spec);
RustcTargetInfo { target_cfgs, metadata: spec.metadata }
} }
fn rustc_stdout(rustc: &Path, args: &[&str]) -> String { fn rustc_stdout(rustc: &Path, args: &[&str]) -> String {

View file

@ -2,7 +2,7 @@
use eyre::{bail, OptionExt, Result, WrapErr}; use eyre::{bail, OptionExt, Result, WrapErr};
use serde::Deserialize; use serde::Deserialize;
use std::{fs::DirEntry, path::Path}; use std::{collections::HashMap, fs::DirEntry, path::Path};
#[derive(Debug, PartialEq, Clone, Deserialize)] #[derive(Debug, PartialEq, Clone, Deserialize)]
pub enum Tier { pub enum Tier {
@ -20,9 +20,10 @@ pub struct ParsedTargetInfoFile {
pub tier: Option<Tier>, pub tier: Option<Tier>,
pub maintainers: Vec<String>, pub maintainers: Vec<String>,
pub sections: Vec<(String, String)>, pub sections: Vec<(String, String)>,
pub metadata: Vec<ParsedTargetMetadata>, pub footnotes: HashMap<String, Vec<String>>,
} }
// IMPORTANT: This is also documented in the README, keep it in sync.
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct Frontmatter { struct Frontmatter {
@ -30,27 +31,19 @@ struct Frontmatter {
#[serde(default)] #[serde(default)]
maintainers: Vec<String>, maintainers: Vec<String>,
#[serde(default)] #[serde(default)]
metadata: Vec<ParsedTargetMetadata>, footnotes: HashMap<String, Vec<String>>,
} }
// IMPORTANT: This is also documented in the README, keep it in sync.
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ParsedTargetMetadata { pub struct TargetFootnotes {
pub pattern: String, pub target: String,
pub notes: String,
pub std: TriStateBool,
pub host: TriStateBool,
#[serde(default)] #[serde(default)]
pub footnotes: Vec<Footnote>, pub footnotes: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Footnote {
pub name: String,
pub content: String,
} }
// IMPORTANT: This is also documented in the README, keep it in sync.
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)] #[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum TriStateBool { pub enum TriStateBool {
@ -89,9 +82,7 @@ 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 let frontmatter = frontmatter_splitter.nth(1).ok_or_eyre("missing frontmatter")?;
.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 ---
@ -149,99 +140,16 @@ fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
} }
} }
sections sections.iter_mut().for_each(|section| section.1 = section.1.trim().to_owned());
.iter_mut()
.for_each(|section| section.1 = section.1.trim().to_owned());
Ok(ParsedTargetInfoFile { Ok(ParsedTargetInfoFile {
pattern: name.to_owned(), pattern: name.to_owned(),
maintainers: frontmatter.maintainers, maintainers: frontmatter.maintainers,
tier: frontmatter.tier, tier: frontmatter.tier,
sections, sections,
metadata: frontmatter.metadata, footnotes: frontmatter.footnotes,
}) })
} }
#[cfg(test)] #[cfg(test)]
mod tests { 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(),
),
]
);
}
}

80
src/parse/tests.rs Normal file
View file

@ -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(),
),
]
);
}

View file

@ -2,16 +2,13 @@ use eyre::{Context, OptionExt, Result};
use std::{fs, path::Path}; use std::{fs, path::Path};
use crate::{ use crate::{
is_in_rust_lang_rust,
parse::{Footnote, Tier, TriStateBool}, parse::{Footnote, Tier, TriStateBool},
RustcTargetInfo, TargetDocs, RustcTargetInfo, TargetDocs,
}; };
impl TargetDocs { impl TargetDocs {
fn has_host_tools(&self) -> bool { fn has_host_tools(&self) -> bool {
self.metadata self.metadata.as_ref().map_or(false, |meta| meta.host == TriStateBool::True)
.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); section("Maintainers", &maintainers_content);
for section_name in crate::SECTIONS { for section_name in crate::SECTIONS {
let value = target let value = target.sections.iter().find(|(name, _)| name == section_name);
.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(),
@ -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. /// 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 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")?;
@ -125,26 +123,8 @@ pub fn render_static(src_output: &Path, targets: &[(TargetDocs, RustcTargetInfo)
let new_targets = let new_targets =
replace_section(&old_targets, "TARGET", &target_list).wrap_err("replacing targets.md")?; replace_section(&old_targets, "TARGET", &target_list).wrap_err("replacing targets.md")?;
fs::write(targets_file, new_targets).wrap_err("writing targets.md")?; if !check_only {
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 <https://github.com/Nilstrieb/target-tier-docs-experiment> 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")?;
} }
let platform_support_main = src_output.join("platform-support.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")?; fs::read_to_string(&platform_support_main).wrap_err("reading platform-support.md")?;
let platform_support_main_new = let platform_support_main_new =
render_platform_support_tables(&platform_support_main_old, targets)?; 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(()) Ok(())
} }
@ -227,47 +210,35 @@ struct TierTable {
include_host: bool, include_host: bool,
} }
fn render_table<'a>(targets: &[(TargetDocs, RustcTargetInfo)], table: TierTable) -> Result<String> { fn render_table(targets: &[(TargetDocs, RustcTargetInfo)], table: TierTable) -> Result<String> {
let mut rows = Vec::new(); let mut rows = Vec::new();
let mut all_footnotes = Vec::new(); let mut all_footnotes = Vec::new();
let targets = targets let targets = targets.into_iter().filter(|target| (table.filter)(&target.0));
.into_iter()
.filter(|target| (table.filter)(&target.0));
for (target, _) in targets { for (target, _) in targets {
let meta = target.metadata.as_ref(); let meta = target.metadata.as_ref();
let mut notes = meta let mut notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown").to_owned();
.map(|meta| meta.notes.as_str())
.unwrap_or("unknown")
.to_owned();
if meta.map_or(false, |meta| !meta.footnotes.is_empty()) { if meta.map_or(false, |meta| !meta.footnotes.is_empty()) {
let footnotes = &meta.unwrap().footnotes; let footnotes = &meta.unwrap().footnotes;
all_footnotes.extend(footnotes); all_footnotes.extend(footnotes);
let footnotes_str = footnotes let footnotes_str =
.iter() footnotes.iter().map(|footnote| footnote.reference()).collect::<Vec<_>>().join(" ");
.map(|footnote| footnote.reference())
.collect::<Vec<_>>()
.join(" ");
notes = format!("{notes} {footnotes_str}"); notes = format!("{notes} {footnotes_str}");
} }
let std = if table.include_std { let std = if table.include_std {
let std = meta let std = meta.map(|meta| render_table_tri_state_bool(meta.std)).unwrap_or("?");
.map(|meta| render_table_tri_state_bool(meta.std))
.unwrap_or("?");
format!(" | {std}") format!(" | {std}")
} else { } else {
String::new() String::new()
}; };
let host = if table.include_host { let host = if table.include_host {
let host = meta let host = meta.map(|meta| render_table_tri_state_bool(meta.host)).unwrap_or("?");
.map(|meta| render_table_tri_state_bool(meta.host))
.unwrap_or("?");
format!(" | {host}") format!(" | {host}")
} else { } else {
String::new() String::new()