mirror of
https://github.com/Noratrieb/target-tier-docs-experiment.git
synced 2026-01-14 08:30:12 +01:00
sync
This commit is contained in:
parent
a6884454c0
commit
720b23bd8e
7 changed files with 249 additions and 295 deletions
64
Cargo.lock
generated
64
Cargo.lock
generated
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
69
README.md
69
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 <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.
|
||||
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.
|
||||
|
|
|
|||
137
src/main.rs
137
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<String>,
|
||||
sections: Vec<(String, String)>,
|
||||
tier: Option<Tier>,
|
||||
// TODO: Make this mandatory.
|
||||
metadata: Option<TargetMetadata>,
|
||||
}
|
||||
|
||||
/// Metadata for the table
|
||||
struct TargetMetadata {
|
||||
notes: String,
|
||||
std: TriStateBool,
|
||||
host: TriStateBool,
|
||||
footnotes: Vec<Footnote>,
|
||||
footnotes: Vec<String>,
|
||||
}
|
||||
|
||||
/// 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::<Vec<_>>();
|
||||
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::<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))
|
||||
.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::<Vec<_>>();
|
||||
|
||||
eprintln!("Collecting rustc information");
|
||||
let rustc_infos = targets
|
||||
.par_iter()
|
||||
.map(|target| rustc_target_info(&rustc, target))
|
||||
.collect::<Vec<_>>();
|
||||
let rustc_infos =
|
||||
targets.iter().map(|target| rustc_target_info(&rustc, target)).collect::<Vec<_>>();
|
||||
|
||||
let targets = targets
|
||||
.into_iter()
|
||||
|
|
@ -90,42 +67,39 @@ fn main() -> Result<()> {
|
|||
.zip(rustc_infos)
|
||||
.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 {
|
||||
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<bool>,
|
||||
footnotes_used: Vec<bool>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
tier: Option<u8>,
|
||||
host_tools: Option<bool>,
|
||||
std: Option<bool>,
|
||||
}
|
||||
|
||||
/// 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::<TargetJson>(&json_spec);
|
||||
|
||||
RustcTargetInfo { target_cfgs, metadata: spec.metadata }
|
||||
}
|
||||
|
||||
fn rustc_stdout(rustc: &Path, args: &[&str]) -> String {
|
||||
|
|
|
|||
118
src/parse.rs
118
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<Tier>,
|
||||
pub maintainers: Vec<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)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Frontmatter {
|
||||
|
|
@ -30,27 +31,19 @@ struct Frontmatter {
|
|||
#[serde(default)]
|
||||
maintainers: Vec<String>,
|
||||
#[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)]
|
||||
#[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<Footnote>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Footnote {
|
||||
pub name: String,
|
||||
pub content: String,
|
||||
pub footnotes: Vec<String>,
|
||||
}
|
||||
|
||||
// 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<ParsedTargetInfoFile> {
|
|||
fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
|
||||
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<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 {
|
||||
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;
|
||||
|
|
|
|||
80
src/parse/tests.rs
Normal file
80
src/parse/tests.rs
Normal 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(),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -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 <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")?;
|
||||
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<String> {
|
||||
fn render_table(targets: &[(TargetDocs, RustcTargetInfo)], table: TierTable) -> Result<String> {
|
||||
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::<Vec<_>>()
|
||||
.join(" ");
|
||||
let footnotes_str =
|
||||
footnotes.iter().map(|footnote| footnote.reference()).collect::<Vec<_>>().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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue