Skip to content

Commit 0219a6e

Browse files
committed
add relnotes-api-list tool
1 parent 1e83852 commit 0219a6e

File tree

17 files changed

+1368
-3
lines changed

17 files changed

+1368
-3
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3111,6 +3111,16 @@ version = "0.8.5"
31113111
source = "registry+https://github.com/rust-lang/crates.io-index"
31123112
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
31133113

3114+
[[package]]
3115+
name = "relnotes-api-list"
3116+
version = "0.1.0"
3117+
dependencies = [
3118+
"anyhow",
3119+
"rustdoc-json-types",
3120+
"serde",
3121+
"serde_json",
3122+
]
3123+
31143124
[[package]]
31153125
name = "remote-test-client"
31163126
version = "0.1.0"

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ members = [
3030
"src/tools/miri/cargo-miri",
3131
"src/tools/miropt-test-tools",
3232
"src/tools/opt-dist",
33+
"src/tools/relnotes-api-list",
3334
"src/tools/remote-test-client",
3435
"src/tools/remote-test-server",
3536
"src/tools/replace-version-placeholder",

src/bootstrap/src/core/build_steps/dist.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,3 +2616,49 @@ impl Step for Gcc {
26162616
tarball.generate()
26172617
}
26182618
}
2619+
2620+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
2621+
pub struct RelnotesApiList {
2622+
pub host: TargetSelection,
2623+
}
2624+
2625+
impl Step for RelnotesApiList {
2626+
type Output = ();
2627+
const DEFAULT: bool = true;
2628+
2629+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
2630+
let default = run.builder.config.docs;
2631+
run.alias("relnotes-api-list").default_condition(default)
2632+
}
2633+
2634+
fn make_run(run: RunConfig<'_>) {
2635+
run.builder.ensure(RelnotesApiList { host: run.target });
2636+
}
2637+
2638+
fn run(self, builder: &Builder<'_>) -> Self::Output {
2639+
let host = self.host;
2640+
let dest = builder.out.join("dist").join(format!("relnotes-api-list-{host}.json"));
2641+
builder.create_dir(dest.parent().unwrap());
2642+
2643+
if std::env::var_os("EMILY_SKIP_DOC").is_none() { // TODO: remove the condition
2644+
builder.ensure(
2645+
crate::core::build_steps::doc::Std::new(
2646+
builder.top_stage,
2647+
host,
2648+
DocumentationFormat::Json,
2649+
)
2650+
// Crates containing symbols exported by any std crate:
2651+
.add_extra_crate("rustc-literal-escaper")
2652+
.add_extra_crate("std_detect"),
2653+
);
2654+
}
2655+
2656+
builder.info("Generating the API list for the release notes");
2657+
builder
2658+
.tool_cmd(Tool::RelnotesApiList)
2659+
.arg(builder.json_doc_out(host))
2660+
.arg(&dest)
2661+
.run(builder);
2662+
builder.info(&format!("API list for the release notes available at {}", dest.display()));
2663+
}
2664+
}

src/bootstrap/src/core/build_steps/doc.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,11 +559,17 @@ pub struct Std {
559559
pub target: TargetSelection,
560560
pub format: DocumentationFormat,
561561
crates: Vec<String>,
562+
extra_crates: Vec<String>,
562563
}
563564

564565
impl Std {
565566
pub(crate) fn new(stage: u32, target: TargetSelection, format: DocumentationFormat) -> Self {
566-
Std { stage, target, format, crates: vec![] }
567+
Std { stage, target, format, crates: vec![], extra_crates: vec![] }
568+
}
569+
570+
pub(crate) fn add_extra_crate(mut self, krate: &str) -> Self {
571+
self.extra_crates.push(krate.to_string());
572+
self
567573
}
568574
}
569575

@@ -591,6 +597,7 @@ impl Step for Std {
591597
DocumentationFormat::Html
592598
},
593599
crates,
600+
extra_crates: vec![],
594601
});
595602
}
596603

@@ -601,7 +608,7 @@ impl Step for Std {
601608
fn run(self, builder: &Builder<'_>) {
602609
let stage = self.stage;
603610
let target = self.target;
604-
let crates = if self.crates.is_empty() {
611+
let mut crates = if self.crates.is_empty() {
605612
builder
606613
.in_tree_crates("sysroot", Some(target))
607614
.iter()
@@ -610,6 +617,7 @@ impl Step for Std {
610617
} else {
611618
self.crates
612619
};
620+
crates.extend(self.extra_crates.iter().cloned());
613621

614622
let out = match self.format {
615623
DocumentationFormat::Html => builder.doc_out(target),

src/bootstrap/src/core/build_steps/tool.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ bootstrap_tool!(
519519
FeaturesStatusDump, "src/tools/features-status-dump", "features-status-dump";
520520
OptimizedDist, "src/tools/opt-dist", "opt-dist", submodules = &["src/tools/rustc-perf"];
521521
RunMakeSupport, "src/tools/run-make-support", "run_make_support", artifact_kind = ToolArtifactKind::Library;
522+
RelnotesApiList, "src/tools/relnotes-api-list", "relnotes-api-list";
522523
);
523524

524525
/// These are the submodules that are required for rustbook to work due to

src/bootstrap/src/core/builder/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,7 @@ impl<'a> Builder<'a> {
952952
tool::CoverageDump,
953953
tool::LlvmBitcodeLinker,
954954
tool::RustcPerf,
955+
tool::RelnotesApiList,
955956
),
956957
Kind::Clippy => describe!(
957958
clippy::Std,
@@ -1128,7 +1129,8 @@ impl<'a> Builder<'a> {
11281129
dist::PlainSourceTarball,
11291130
dist::BuildManifest,
11301131
dist::ReproducibleArtifacts,
1131-
dist::Gcc
1132+
dist::Gcc,
1133+
dist::RelnotesApiList,
11321134
),
11331135
Kind::Install => describe!(
11341136
install::Docs,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "relnotes-api-list"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
anyhow = "1"
8+
serde = { version = "1", features = ["derive"] }
9+
serde_json = "1"
10+
rustdoc-json-types = { path = "../../rustdoc-json-types" }

src/tools/relnotes-api-list/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# API list generator for the release notes
2+
3+
Rust's [release notes] include a "Stabilized APIs" section for each
4+
release, listing all APIs that became stable each release. This tool supports
5+
the creation of that section by generating a JSON file containing a concise
6+
representation of the standard library API. The [relnotes tool] will then
7+
compare the JSON files of two releases to generate the section.
8+
9+
The tool is executed by CI and produces the `relnotes-api-list-$target.json`
10+
dist artifact. You can also run the tool locally with:
11+
12+
```
13+
./x dist relnotes-api-list
14+
```
15+
16+
[release notes]: https://doc.rust-lang.org/stable/releases.html
17+
[relnotes tool]: https://github.com/rust-lang/relnotes
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use crate::pretty_print::pretty_impl;
2+
use crate::schema::SchemaItem;
3+
use crate::stability::{Stability, StabilityStore};
4+
use crate::store::{Store, StoreItem};
5+
use crate::url::UrlStore;
6+
use crate::visitor::{Visitor, walk_item};
7+
use anyhow::{Error, bail};
8+
use rustdoc_json_types::{ItemEnum, Visibility};
9+
10+
pub(crate) struct ConvertToSchema<'a> {
11+
store: &'a Store,
12+
stability: &'a StabilityStore,
13+
urls: &'a UrlStore,
14+
15+
name_stack: Vec<String>,
16+
}
17+
18+
impl<'a> Visitor<'a> for ConvertToSchema<'a> {
19+
type Result = Vec<SchemaItem>;
20+
21+
fn visit_item(&mut self, item: &StoreItem<'a>) -> Result<Vec<SchemaItem>, Error> {
22+
if item.visibility != expected_visibility(item) {
23+
return Ok(Vec::new());
24+
}
25+
26+
match (self.stability.get(item.crate_id, item.id), &item.inner) {
27+
(Some(Stability::Stable), _) => {}
28+
(Some(Stability::Unstable), _) => return Ok(Vec::new()),
29+
30+
// `impl` blocks are not required to have stability attributes, so we should not error
31+
// out if they are missing them.
32+
(None, ItemEnum::Impl(_)) => {}
33+
34+
(None, _) => bail!(
35+
"stability attribute is missing for {} {:?}",
36+
self.name_stack.join("::"),
37+
item.id
38+
),
39+
}
40+
41+
let mut pop_name = false;
42+
if let Some(name) = &item.name {
43+
self.name_stack.push(name.clone());
44+
pop_name = true;
45+
}
46+
47+
let result = match &item.inner {
48+
ItemEnum::AssocConst { .. }
49+
| ItemEnum::AssocType { .. }
50+
| ItemEnum::Constant { .. }
51+
| ItemEnum::Enum(_)
52+
| ItemEnum::Macro(_)
53+
| ItemEnum::Module(_)
54+
| ItemEnum::Static(_)
55+
| ItemEnum::Struct(_)
56+
| ItemEnum::StructField(_)
57+
| ItemEnum::Trait(_)
58+
| ItemEnum::TypeAlias(_)
59+
| ItemEnum::Union(_)
60+
| ItemEnum::Variant(_)
61+
| ItemEnum::ProcMacro(_)
62+
| ItemEnum::Function(_) => self.include(item),
63+
64+
ItemEnum::Primitive(p) => {
65+
// We don't want primitives to have the `std::` prefix.
66+
let old_stack = std::mem::replace(&mut self.name_stack, vec![p.name.clone()]);
67+
let result = self.include(item);
68+
self.name_stack = old_stack;
69+
result
70+
}
71+
72+
ItemEnum::Use(_) => walk_item(self, item),
73+
74+
ItemEnum::Impl(impl_) => {
75+
if impl_.trait_.is_some() {
76+
Ok(vec![SchemaItem {
77+
name: pretty_impl(impl_),
78+
deprecated: item.deprecation.is_some(),
79+
url: None, // TODO: is there an URL we can put here?
80+
81+
// We are intentionally not walking inside impls of traits: we don't want
82+
// all types in the standard library to show up in the changelog if a new
83+
// item is added in a trait.
84+
children: Vec::new(),
85+
}])
86+
} else {
87+
walk_item(self, item)
88+
}
89+
}
90+
91+
ItemEnum::TraitAlias(_) | ItemEnum::ExternType | ItemEnum::ExternCrate { .. } => {
92+
Ok(Vec::new())
93+
}
94+
};
95+
96+
if pop_name {
97+
self.name_stack.pop();
98+
}
99+
result
100+
}
101+
102+
fn store(&self) -> &'a Store {
103+
self.store
104+
}
105+
}
106+
107+
impl<'a> ConvertToSchema<'a> {
108+
pub(crate) fn new(store: &'a Store, stability: &'a StabilityStore, urls: &'a UrlStore) -> Self {
109+
Self { store, stability, urls, name_stack: Vec::new() }
110+
}
111+
112+
fn include(&mut self, item: &StoreItem<'a>) -> Result<Vec<SchemaItem>, Error> {
113+
let item = SchemaItem {
114+
name: self.name_stack.join("::"),
115+
url: Some(self.urls.for_item(item)?.into()),
116+
deprecated: item.deprecation.is_some(),
117+
children: walk_item(self, item)?,
118+
};
119+
Ok(vec![item])
120+
}
121+
}
122+
123+
/// Some items don't have a visibility associated to them, and are instead public by default. This
124+
/// function determines what visibility a public item must have.
125+
fn expected_visibility(item: &StoreItem<'_>) -> Visibility {
126+
match &item.inner {
127+
ItemEnum::AssocType { .. }
128+
| ItemEnum::AssocConst { .. }
129+
| ItemEnum::Variant(_)
130+
| ItemEnum::Impl(_) => Visibility::Default,
131+
132+
ItemEnum::Module(_)
133+
| ItemEnum::ExternCrate { .. }
134+
| ItemEnum::Use(_)
135+
| ItemEnum::Union(_)
136+
| ItemEnum::Struct(_)
137+
| ItemEnum::StructField(_)
138+
| ItemEnum::Enum(_)
139+
| ItemEnum::Function(_)
140+
| ItemEnum::Trait(_)
141+
| ItemEnum::TraitAlias(_)
142+
| ItemEnum::TypeAlias(_)
143+
| ItemEnum::Constant { .. }
144+
| ItemEnum::Static(_)
145+
| ItemEnum::ExternType
146+
| ItemEnum::Macro(_)
147+
| ItemEnum::ProcMacro(_)
148+
| ItemEnum::Primitive(_) => Visibility::Public,
149+
}
150+
}

0 commit comments

Comments
 (0)