Skip to content

Commit 016bf56

Browse files
committed
Prevent rustup update to a toolchain without rustc or cargo.
When the Rust CI is misconfigured, it is possible to produce a nightly where `rustc` or `cargo` is not available. This makes the entire toolchain unusable (for one day at least). This PR tries to prevent the update operation from starting at all if these essential components are missing, so the user would still have a working though outdated toolchain. Resolves #1297.
1 parent a1f0d0d commit 016bf56

File tree

5 files changed

+138
-53
lines changed

5 files changed

+138
-53
lines changed

src/rustup-dist/src/manifestation.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,22 @@ impl Manifestation {
107107
return Ok(UpdateStatus::Unchanged);
108108
}
109109

110+
// Make sure we don't accidentally uninstall the essential components! (see #1297)
111+
let missing_essential_components = ["rustc", "cargo"]
112+
.iter()
113+
.filter_map(|pkg| if final_component_list.iter().any(|c| &c.pkg == pkg) {
114+
None
115+
} else {
116+
Some(Component {
117+
pkg: pkg.to_string(),
118+
target: Some(self.target_triple.clone()),
119+
})
120+
})
121+
.collect::<Vec<_>>();
122+
if !missing_essential_components.is_empty() {
123+
return Err(ErrorKind::RequestedComponentsUnavailable(missing_essential_components).into());
124+
}
125+
110126
// Validate that the requested components are available
111127
let unavailable_components: Vec<Component> = components_to_install.iter().filter(|c| {
112128
use manifest::*;

src/rustup-dist/tests/dist.rs

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ pub fn create_mock_channel(channel: &str, date: &str,
5151
// Put the date in the files so they can be differentiated
5252
let contents = Arc::new(date.as_bytes().to_vec());
5353

54-
let rust_pkg = MockPackage {
54+
let mut packages = Vec::with_capacity(5);
55+
56+
packages.push(MockPackage {
5557
name: "rust",
5658
version: "1.0.0",
5759
targets: vec![
@@ -63,6 +65,10 @@ pub fn create_mock_channel(channel: &str, date: &str,
6365
name: "rustc".to_string(),
6466
target: "x86_64-apple-darwin".to_string(),
6567
},
68+
MockComponent {
69+
name: "cargo".to_string(),
70+
target: "x86_64-apple-darwin".to_string(),
71+
},
6672
MockComponent {
6773
name: "rust-std".to_string(),
6874
target: "x86_64-apple-darwin".to_string(),
@@ -90,6 +96,10 @@ pub fn create_mock_channel(channel: &str, date: &str,
9096
name: "rustc".to_string(),
9197
target: "i686-apple-darwin".to_string(),
9298
},
99+
MockComponent {
100+
name: "cargo".to_string(),
101+
target: "i686-apple-darwin".to_string(),
102+
},
93103
MockComponent {
94104
name: "rust-std".to_string(),
95105
target: "i686-apple-darwin".to_string(),
@@ -101,39 +111,42 @@ pub fn create_mock_channel(channel: &str, date: &str,
101111
}
102112
}
103113
]
104-
};
114+
});
105115

106-
let rustc_pkg = MockPackage {
107-
name: "rustc",
108-
version: "1.0.0",
109-
targets: vec![
110-
MockTargetedPackage {
111-
target: "x86_64-apple-darwin".to_string(),
112-
available: true,
113-
components: vec![],
114-
extensions: vec![],
115-
installer: MockInstallerBuilder {
116-
components: vec![MockComponentBuilder {
117-
name: "rustc".to_string(),
118-
files: vec![
119-
MockFile::new_arc("bin/rustc", contents.clone()),
120-
],
121-
}],
122-
}
123-
},
124-
MockTargetedPackage {
125-
target: "i686-apple-darwin".to_string(),
126-
available: true,
127-
components: vec![],
128-
extensions: vec![],
129-
installer: MockInstallerBuilder {
130-
components: vec![]
131-
}
132-
}
133-
]
134-
};
116+
for bin in &["bin/rustc", "bin/cargo"] {
117+
let pkg = &bin[4..];
118+
packages.push(MockPackage {
119+
name: pkg,
120+
version: "1.0.0",
121+
targets: vec![
122+
MockTargetedPackage {
123+
target: "x86_64-apple-darwin".to_string(),
124+
available: true,
125+
components: vec![],
126+
extensions: vec![],
127+
installer: MockInstallerBuilder {
128+
components: vec![MockComponentBuilder {
129+
name: pkg.to_string(),
130+
files: vec![
131+
MockFile::new_arc(*bin, contents.clone()),
132+
],
133+
}],
134+
}
135+
},
136+
MockTargetedPackage {
137+
target: "i686-apple-darwin".to_string(),
138+
available: true,
139+
components: vec![],
140+
extensions: vec![],
141+
installer: MockInstallerBuilder {
142+
components: vec![]
143+
}
144+
},
145+
],
146+
});
147+
}
135148

136-
let std_pkg = MockPackage {
149+
packages.push(MockPackage {
137150
name: "rust-std",
138151
version: "1.0.0",
139152
targets: vec![
@@ -180,26 +193,20 @@ pub fn create_mock_channel(channel: &str, date: &str,
180193
}
181194
},
182195
]
183-
};
196+
});
184197

185198
// An extra package that can be used as a component of the other packages
186199
// for various tests
187-
let bonus_pkg = bonus_component("bonus", contents.clone());
200+
packages.push(bonus_component("bonus", contents.clone()));
188201

189-
let mut rust_pkg = rust_pkg;
190202
if let Some(edit) = edit {
191-
edit(date, &mut rust_pkg);
203+
edit(date, &mut packages[0]);
192204
}
193205

194206
MockChannel {
195207
name: channel.to_string(),
196208
date: date.to_string(),
197-
packages: vec![
198-
rust_pkg,
199-
rustc_pkg,
200-
std_pkg,
201-
bonus_pkg,
202-
],
209+
packages,
203210
renames: HashMap::new(),
204211
}
205212
}
@@ -270,7 +277,7 @@ fn rename_component() {
270277

271278
let date_2 = "2016-02-02";
272279
let mut channel_2 = create_mock_channel("nightly", date_2, Some(edit_2));
273-
channel_2.packages[3] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec()));
280+
channel_2.packages[4] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec()));
274281
channel_2.renames.insert("bonus".to_owned(), "bobo".to_owned());
275282
let mock_dist_server = MockDistServer {
276283
path: dist_tempdir.path().to_owned(),
@@ -309,10 +316,10 @@ fn rename_component_ignore() {
309316

310317
let date_1 = "2016-02-01";
311318
let mut channel_1 = create_mock_channel("nightly", date_1, Some(edit));
312-
channel_1.packages[3] = bonus_component("bobo", Arc::new(date_1.as_bytes().to_vec()));
319+
channel_1.packages[4] = bonus_component("bobo", Arc::new(date_1.as_bytes().to_vec()));
313320
let date_2 = "2016-02-02";
314321
let mut channel_2 = create_mock_channel("nightly", date_2, Some(edit));
315-
channel_2.packages[3] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec()));
322+
channel_2.packages[4] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec()));
316323
channel_2.renames.insert("bonus".to_owned(), "bobo".to_owned());
317324
let mock_dist_server = MockDistServer {
318325
path: dist_tempdir.path().to_owned(),
@@ -351,7 +358,7 @@ fn rename_component_new() {
351358

352359
let date_2 = "2016-02-02";
353360
let mut channel_2 = create_mock_channel("nightly", date_2, Some(edit_2));
354-
channel_2.packages[3] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec()));
361+
channel_2.packages[4] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec()));
355362
channel_2.renames.insert("bonus".to_owned(), "bobo".to_owned());
356363
let mock_dist_server = MockDistServer {
357364
path: dist_tempdir.path().to_owned(),

src/rustup-mock/src/clitools.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub enum Scenario {
5050
SimpleV2, // One date, v2 manifests
5151
SimpleV1, // One date, v1 manifests
5252
MultiHost, // One date, v2 manifests, MULTI_ARCH1 host
53+
Unavailable, // Two dates, v2 manifests, everything unavailable in second date.
5354
}
5455

5556
pub static CROSS_ARCH1: &'static str = "x86_64-unknown-linux-musl";
@@ -333,13 +334,23 @@ pub fn run(config: &Config, name: &str, args: &[&str], env: &[(&str, &str)]) ->
333334
// Creates a mock dist server populated with some test data
334335
fn create_mock_dist_server(path: &Path, s: Scenario) {
335336
let mut chans = Vec::new();
336-
if s == Scenario::Full || s == Scenario::ArchivesV1 || s == Scenario::ArchivesV2 {
337+
338+
let dates_count = match s {
339+
Scenario::SimpleV1 | Scenario::SimpleV2 | Scenario::MultiHost => 1,
340+
Scenario::Full | Scenario::ArchivesV1 | Scenario::ArchivesV2 | Scenario::Unavailable => 2,
341+
};
342+
343+
if dates_count > 1 {
337344
let c1 = build_mock_channel(s, "nightly", "2015-01-01", "1.2.0", "hash-n-1", false);
338345
let c2 = build_mock_channel(s, "beta", "2015-01-01", "1.1.0", "hash-b-1", false);
339346
let c3 = build_mock_channel(s, "stable", "2015-01-01", "1.0.0", "hash-s-1", false);
340347
chans.extend(vec![c1, c2, c3]);
341348
}
342-
let c4 = build_mock_channel(s, "nightly", "2015-01-02", "1.3.0", "hash-n-2", true);
349+
let c4 = if s == Scenario::Unavailable {
350+
build_mock_unavailable_channel("nightly", "2015-01-02", "1.3.0")
351+
} else {
352+
build_mock_channel(s, "nightly", "2015-01-02", "1.3.0", "hash-n-2", true)
353+
};
343354
let c5 = build_mock_channel(s, "beta", "2015-01-02", "1.2.0", "hash-b-2", false);
344355
let c6 = build_mock_channel(s, "stable", "2015-01-02", "1.1.0", "hash-s-2", false);
345356
chans.extend(vec![c4, c5, c6]);
@@ -348,7 +359,7 @@ fn create_mock_dist_server(path: &Path, s: Scenario) {
348359
Scenario::Full => vec![ManifestVersion::V1, ManifestVersion::V2],
349360
Scenario::SimpleV1 | Scenario::ArchivesV1 => vec![ManifestVersion::V1],
350361
Scenario::SimpleV2 | Scenario::ArchivesV2 |
351-
Scenario::MultiHost => vec![ManifestVersion::V2],
362+
Scenario::MultiHost | Scenario::Unavailable => vec![ManifestVersion::V2],
352363
};
353364

354365
MockDistServer {
@@ -357,7 +368,7 @@ fn create_mock_dist_server(path: &Path, s: Scenario) {
357368
}.write(vs, true);
358369

359370
// Also create the manifests for stable releases by version
360-
if s == Scenario::Full || s == Scenario::ArchivesV1 || s == Scenario::ArchivesV2 {
371+
if dates_count > 1 {
361372
let _ = hard_link(path.join("dist/2015-01-01/channel-rust-stable.toml"),
362373
path.join("dist/channel-rust-1.0.0.toml"));
363374
let _ = hard_link(path.join("dist/2015-01-01/channel-rust-stable.toml.sha256"),
@@ -370,7 +381,7 @@ fn create_mock_dist_server(path: &Path, s: Scenario) {
370381

371382
// Same for v1 manifests. These are just the installers.
372383
let host_triple = this_host_triple();
373-
if s == Scenario::Full || s == Scenario::ArchivesV1 || s == Scenario::ArchivesV2 {
384+
if dates_count > 1 {
374385
hard_link(path.join(format!("dist/2015-01-01/rust-stable-{}.tar.gz", host_triple)),
375386
path.join(format!("dist/rust-1.0.0-{}.tar.gz", host_triple))).unwrap();
376387
hard_link(path.join(format!("dist/2015-01-01/rust-stable-{}.tar.gz.sha256", host_triple)),
@@ -534,6 +545,40 @@ fn build_mock_channel(s: Scenario, channel: &str, date: &str,
534545
}
535546
}
536547

548+
fn build_mock_unavailable_channel(channel: &str, date: &str, version: &'static str) -> MockChannel {
549+
let ref host_triple = this_host_triple();
550+
551+
let packages = [
552+
"cargo",
553+
"rust",
554+
"rust-docs",
555+
"rust-std",
556+
"rustc",
557+
"rls-preview",
558+
"rust-analysis",
559+
];
560+
let packages = packages.iter().map(|name| MockPackage {
561+
name,
562+
version,
563+
targets: vec![MockTargetedPackage {
564+
target: host_triple.clone(),
565+
available: false,
566+
components: vec![],
567+
extensions: vec![],
568+
installer: MockInstallerBuilder {
569+
components: vec![],
570+
},
571+
}],
572+
}).collect();
573+
574+
MockChannel {
575+
name: channel.to_string(),
576+
date: date.to_string(),
577+
packages,
578+
renames: HashMap::new(),
579+
}
580+
}
581+
537582
pub fn this_host_triple() -> String {
538583
if let Some(triple) = option_env!("RUSTUP_OVERRIDE_BUILD_TRIPLE") {
539584
triple.to_owned()

tests/cli-misc.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,20 @@ r"info: no toolchain installed for 'test'
567567
");
568568
});
569569
}
570+
571+
// issue #1297
572+
#[test]
573+
fn update_unavailable_rustc() {
574+
clitools::setup(Scenario::Unavailable, &|config| {
575+
set_current_dist_date(config, "2015-01-01");
576+
expect_ok(config, &["rustup", "default", "nightly"]);
577+
578+
expect_stdout_ok(config, &["rustc", "--version"], "hash-n-1");
579+
580+
set_current_dist_date(config, "2015-01-02");
581+
expect_err(config, &["rustup", "update", "nightly"],
582+
"some components unavailable for download: 'rustc', 'cargo'");
583+
584+
expect_stdout_ok(config, &["rustc", "--version"], "hash-n-1");
585+
});
586+
}

tests/cli-v2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,8 +683,8 @@ fn make_component_unavailable(config: &Config, name: &str, target: &TargetTriple
683683
let ref manifest_str = rustup_utils::raw::read_file(manifest_path).unwrap();
684684
let mut manifest = Manifest::parse(manifest_str).unwrap();
685685
{
686-
let mut std_pkg = manifest.packages.get_mut(name).unwrap();
687-
let mut target_pkg = std_pkg.targets.get_mut(target).unwrap();
686+
let std_pkg = manifest.packages.get_mut(name).unwrap();
687+
let target_pkg = std_pkg.targets.get_mut(target).unwrap();
688688
target_pkg.bins = None;
689689
}
690690
let ref manifest_str = manifest.stringify();

0 commit comments

Comments
 (0)