Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,8 +1122,8 @@ impl<'a> Cfg<'a> {
Err(e) => return Err(anyhow!(e).context("while looking for local stable toolchain")),
};
let release_date_str = match stable.get_manifestation() {
Ok(manifestation) => match manifestation.load_manifest() {
Ok(Some(manifest)) => manifest.date,
Ok(manifestation) => match manifestation.load_manifest_date() {
Ok(Some(date)) => date,
Ok(None) | Err(_) => FALLBACK_RELEASE_DATE.to_owned(),
},
Err(_) => FALLBACK_RELEASE_DATE.to_owned(),
Expand Down
4 changes: 4 additions & 0 deletions src/dist/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ impl Manifest {
Ok(manifest)
}

pub(crate) fn parse_date(header: &str) -> anyhow::Result<String> {
Ok(toml::from_str::<Self>(header)?.date)
}

pub fn stringify(self) -> anyhow::Result<String> {
Ok(toml::to_string(&self)?)
}
Expand Down
51 changes: 49 additions & 2 deletions src/dist/manifestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod tests;

use std::{
collections::VecDeque,
fs,
io::{BufRead, BufReader, Read},
path::Path,
pin::Pin,
sync::Arc,
Expand Down Expand Up @@ -36,8 +38,6 @@ use crate::{
utils,
};

pub(crate) const CONFIG_FILE: &str = "multirust-config.toml";

#[derive(Debug)]
pub struct Manifestation {
installation: Components,
Expand Down Expand Up @@ -378,6 +378,26 @@ impl Manifestation {
}
}

#[tracing::instrument(level = "trace")]
pub(crate) fn load_manifest_date(&self) -> anyhow::Result<Option<String>> {
let prefix = self.installation.prefix();
if let Some(manifest_path) = prefix.dist_manifest() {
let date = fs::File::open(&manifest_path)
.map_err(anyhow::Error::from)

@rami3l rami3l Nov 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This .map_err() looks redundant because it is immediately consumed by debug!() afterwards?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conversion is required by and_then: File::open() returns
std::io::Error, while read_manifest_date() returns anyhow::Error.
Removing it fails to compile because and_then requires the same error
type on both sides. Keeping the errors separate would require nested
matches and duplicate the fallback logging, so the current form seems
a little simpler.

.and_then(read_manifest_date);
match date {
Ok(date) => return Ok(Some(date)),
Err(error) => debug!(
path = %manifest_path.display(),
%error,
"failed to read manifest date from header; falling back to full manifest parsing"
),
}
}

Ok(self.load_manifest()?.map(|manifest| manifest.date))
}

/// Installation using the legacy v1 manifest format
pub(crate) async fn update_v1(
&self,
Expand Down Expand Up @@ -471,6 +491,26 @@ impl Manifestation {
}
}

/// Parses the manifest date from the root-level fields before the first line
/// whose first non-whitespace character is `[`, reading at most
/// `MANIFEST_HEADER_SIZE_LIMIT` bytes.
fn read_manifest_date(reader: impl Read) -> anyhow::Result<String> {
Comment thread
reidliu41 marked this conversation as resolved.
Resolved
let mut reader = BufReader::new(reader.take(MANIFEST_HEADER_SIZE_LIMIT));
let mut header = String::new();
loop {
let line_start = header.len();
if reader.read_line(&mut header)? == 0 {
break;
}
if header[line_start..].trim_start().starts_with('[') {
Comment thread
reidliu41 marked this conversation as resolved.
Resolved
Comment thread
reidliu41 marked this conversation as resolved.
Resolved
header.truncate(line_start);
break;
}
}

Manifest::parse_date(&header)
}

struct InstallEvents<'a, F> {
manifestation: Arc<Manifestation>,
components: vec::IntoIter<ComponentBinary<'a>>,
Expand Down Expand Up @@ -839,5 +879,12 @@ impl ComponentInstall {
}
}

pub(crate) const CONFIG_FILE: &str = "multirust-config.toml";

// The serialized manifest currently places `date` within the first 64 bytes.
// Leave some headroom for reordered or additional root keys; if the header
// exceeds this limit, loading falls back to parsing the full manifest.
const MANIFEST_HEADER_SIZE_LIMIT: u64 = 128;

#[cfg(feature = "test")]
pub const CHECKPOINT_UPDATE_BEFORE_METADATA: &str = "update-before-metadata";
12 changes: 12 additions & 0 deletions src/dist/manifestation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::{
use anyhow::anyhow;
Comment thread
reidliu41 marked this conversation as resolved.
Resolved
use url::Url;

use super::read_manifest_date;
use crate::{
config::Cfg,
dist::{
Expand All @@ -38,6 +39,17 @@ use crate::{

const SHA256_HASH_LEN: usize = 64;

#[test]
fn read_manifest_date_from_serialized_manifest() -> anyhow::Result<()> {
let serialized = Manifest::parse(include_str!(
"../manifest/tests/channel-rust-nightly-example.toml"
))?
.stringify()?;

assert_eq!(read_manifest_date(serialized.as_bytes())?, "2015-10-10");
Ok(())
}

// Creates a mock dist server populated with some test data
fn create_mock_dist_server(
path: &Path,
Expand Down
Loading