Skip to content

Commit fd6a84a

Browse files
committed
Add chapters on dependency resolution and SemVer compatibility.
1 parent 449743b commit fd6a84a

File tree

9 files changed

+1982
-0
lines changed

9 files changed

+1982
-0
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ jobs:
9292
- run: rustup update nightly && rustup default nightly
9393
- run: rustup component add rust-docs
9494
- run: ci/validate-man.sh
95+
- run: cd src/doc/semver-check && cargo run
9596
- run: |
9697
mkdir mdbook
9798
curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.3.7/mdbook-v0.3.7-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook

src/doc/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ To rebuild the man pages, run the script `build-man.sh` in the `src/doc` directo
5656
$ ./build-man.sh
5757
```
5858

59+
### SemVer chapter tests
60+
61+
There is a script to verify that the examples in the SemVer chapter work as
62+
intended. To run the tests, go into the `semver-check` directory and run
63+
`cargo run`.
64+
5965
## Contributing
6066

6167
We'd love your help with improving the documentation! Please feel free to

src/doc/semver-check/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "semver-check"
3+
version = "0.1.0"
4+
authors = ["Eric Huss"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
tempfile = "3.1.0"

src/doc/semver-check/src/main.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
//! Test runner for the semver compatibility doc chapter.
2+
//!
3+
//! This extracts all the "rust" annotated code blocks and tests that they
4+
//! either fail or succeed as expected. This also checks that the examples are
5+
//! formatted correctly.
6+
//!
7+
//! An example with the word "MINOR" at the top is expected to successfully
8+
//! build against the before and after. Otherwise it should fail. A comment of
9+
//! "// Error:" will check that the given message appears in the error output.
10+
11+
use std::error::Error;
12+
use std::fs;
13+
use std::path::Path;
14+
use std::process::Command;
15+
16+
fn main() {
17+
if let Err(e) = doit() {
18+
println!("error: {}", e);
19+
std::process::exit(1);
20+
}
21+
}
22+
23+
const SEPARATOR: &str = "///////////////////////////////////////////////////////////";
24+
25+
fn doit() -> Result<(), Box<dyn Error>> {
26+
let filename = std::env::args()
27+
.skip(1)
28+
.next()
29+
.unwrap_or_else(|| "../src/reference/semver.md".to_string());
30+
let contents = fs::read_to_string(filename)?;
31+
let mut lines = contents.lines().enumerate();
32+
33+
loop {
34+
// Find a rust block.
35+
let block_start = loop {
36+
match lines.next() {
37+
Some((lineno, line)) => {
38+
if line.trim().starts_with("```rust") && !line.contains("skip") {
39+
break lineno + 1;
40+
}
41+
}
42+
None => return Ok(()),
43+
}
44+
};
45+
// Read in the code block.
46+
let mut block = Vec::new();
47+
loop {
48+
match lines.next() {
49+
Some((_, line)) => {
50+
if line.trim() == "```" {
51+
break;
52+
}
53+
block.push(line);
54+
}
55+
None => {
56+
return Err(format!(
57+
"rust block did not end for example starting on line {}",
58+
block_start
59+
)
60+
.into());
61+
}
62+
}
63+
}
64+
// Split it into the separate source files.
65+
let parts: Vec<_> = block.split(|line| line.trim() == SEPARATOR).collect();
66+
if parts.len() != 4 {
67+
return Err(format!(
68+
"expected 4 sections in example starting on line {}, got {}:\n{:?}",
69+
block_start,
70+
parts.len(),
71+
parts
72+
)
73+
.into());
74+
}
75+
let join = |part: &[&str]| {
76+
let mut result = String::new();
77+
result.push_str("#![allow(unused)]\n#![deny(warnings)]\n");
78+
result.push_str(&part.join("\n"));
79+
if !result.ends_with('\n') {
80+
result.push('\n');
81+
}
82+
result
83+
};
84+
let expect_success = parts[0][0].contains("MINOR");
85+
println!("Running test from line {}", block_start);
86+
if let Err(e) = run_test(
87+
join(parts[1]),
88+
join(parts[2]),
89+
join(parts[3]),
90+
expect_success,
91+
) {
92+
return Err(format!(
93+
"test failed for example starting on line {}: {}",
94+
block_start, e
95+
)
96+
.into());
97+
}
98+
}
99+
}
100+
101+
const CRATE_NAME: &str = "updated_crate";
102+
103+
fn run_test(
104+
before: String,
105+
after: String,
106+
example: String,
107+
expect_success: bool,
108+
) -> Result<(), Box<dyn Error>> {
109+
let tempdir = tempfile::TempDir::new()?;
110+
let before_p = tempdir.path().join("before.rs");
111+
let after_p = tempdir.path().join("after.rs");
112+
let example_p = tempdir.path().join("example.rs");
113+
compile(before, &before_p, CRATE_NAME, false, true)?;
114+
compile(example.clone(), &example_p, "example", true, true)?;
115+
compile(after, &after_p, CRATE_NAME, false, true)?;
116+
compile(example, &example_p, "example", true, expect_success)?;
117+
Ok(())
118+
}
119+
120+
fn check_formatting(path: &Path) -> Result<(), Box<dyn Error>> {
121+
match Command::new("rustfmt")
122+
.args(&["--edition=2018", "--check"])
123+
.arg(path)
124+
.status()
125+
{
126+
Ok(status) => {
127+
if !status.success() {
128+
return Err(format!("failed to run rustfmt: {}", status).into());
129+
}
130+
return Ok(());
131+
}
132+
Err(e) => {
133+
return Err(format!("failed to run rustfmt: {}", e).into());
134+
}
135+
}
136+
}
137+
138+
fn compile(
139+
mut contents: String,
140+
path: &Path,
141+
crate_name: &str,
142+
extern_path: bool,
143+
expect_success: bool,
144+
) -> Result<(), Box<dyn Error>> {
145+
// If the example has an error message, remove it so that it can be
146+
// compared with the actual output, and also to avoid issues with rustfmt
147+
// moving it around.
148+
let expected_error = match contents.find("// Error:") {
149+
Some(index) => {
150+
let start = contents[..index].rfind(|ch| ch != ' ').unwrap();
151+
let end = contents[index..].find('\n').unwrap();
152+
let error = contents[index + 9..index + end].trim().to_string();
153+
contents.replace_range(start + 1..index + end, "");
154+
Some(error)
155+
}
156+
None => None,
157+
};
158+
let crate_type = if contents.contains("fn main()") {
159+
"bin"
160+
} else {
161+
"rlib"
162+
};
163+
164+
fs::write(path, &contents)?;
165+
check_formatting(path)?;
166+
let out_dir = path.parent().unwrap();
167+
let mut cmd = Command::new("rustc");
168+
cmd.args(&[
169+
"--edition=2018",
170+
"--crate-type",
171+
crate_type,
172+
"--crate-name",
173+
crate_name,
174+
"--out-dir",
175+
]);
176+
cmd.arg(&out_dir);
177+
if extern_path {
178+
let epath = out_dir.join(format!("lib{}.rlib", CRATE_NAME));
179+
cmd.arg("--extern")
180+
.arg(format!("{}={}", CRATE_NAME, epath.display()));
181+
}
182+
cmd.arg(path);
183+
let output = cmd.output()?;
184+
let stderr = std::str::from_utf8(&output.stderr).unwrap();
185+
match (output.status.success(), expect_success) {
186+
(true, true) => Ok(()),
187+
(true, false) => Err(format!(
188+
"expected failure, got success {}\n===== Contents:\n{}\n===== Output:\n{}\n",
189+
path.display(),
190+
contents,
191+
stderr
192+
)
193+
.into()),
194+
(false, true) => Err(format!(
195+
"expected success, got error {}\n===== Contents:\n{}\n===== Output:\n{}\n",
196+
path.display(),
197+
contents,
198+
stderr
199+
)
200+
.into()),
201+
(false, false) => {
202+
if expected_error.is_none() {
203+
return Err("failing test should have an \"// Error:\" annotation ".into());
204+
}
205+
let expected_error = expected_error.unwrap();
206+
if !stderr.contains(&expected_error) {
207+
Err(format!(
208+
"expected error message not found in compiler output\nExpected: {}\nGot:\n{}\n",
209+
expected_error, stderr
210+
)
211+
.into())
212+
} else {
213+
Ok(())
214+
}
215+
}
216+
}
217+
}

src/doc/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
* [Source Replacement](reference/source-replacement.md)
3636
* [External Tools](reference/external-tools.md)
3737
* [Registries](reference/registries.md)
38+
* [Dependency Resolution](reference/resolver.md)
39+
* [SemVer Compatibility](reference/semver.md)
3840
* [Unstable Features](reference/unstable.md)
3941

4042
* [Cargo Commands](commands/index.md)

src/doc/src/reference/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ The reference covers the details of various areas of Cargo.
1818
* [Source Replacement](source-replacement.md)
1919
* [External Tools](external-tools.md)
2020
* [Registries](registries.md)
21+
* [Dependency Resolution](resolver.md)
22+
* [SemVer Compatibility](semver.md)
2123
* [Unstable Features](unstable.md)

src/doc/src/reference/manifest.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ Versioning](https://semver.org/), so make sure you follow some basic rules:
9595
traits, fields, types, functions, methods or anything else.
9696
* Use version numbers with three numeric parts such as 1.0.0 rather than 1.0.
9797

98+
See the [Resolver] chapter for more information on how Cargo uses versions to
99+
resolve dependencies, and for guidelines on setting your own version. See the
100+
[Semver compatibility] chapter for more details on exactly what constitutes a
101+
breaking change.
102+
103+
[Resolver]: resolver.md
104+
[Semver compatibility]: semver.md
105+
98106
<a id="the-authors-field-optional"></a>
99107
#### The `authors` field
100108

0 commit comments

Comments
 (0)