Skip to content

Commit 6b2c08c

Browse files
xtask: Add update_sources module
1 parent d7aec5e commit 6b2c08c

2 files changed

Lines changed: 185 additions & 1 deletion

File tree

xtask/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
#[expect(dead_code)]
21
mod github;
2+
#[expect(dead_code)]
3+
mod update_sources;
34

45
use anyhow::{anyhow, bail, Result};
56
use clap::{Args, Parser, Subcommand};

xtask/src/update_sources.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use crate::github::Github;
2+
use anyhow::{bail, Result};
3+
use std::fs;
4+
use std::path::Path;
5+
6+
/// Github added automatic release digests in June 2025:
7+
/// <github.blog/changelog/2025-06-03-releases-now-expose-digests-for-release-assets>
8+
///
9+
/// For older releases, hardcode the SHA-256 digest.
10+
const OLD_RELEASES: [(&str, &str); 6] = [
11+
(
12+
"edk2-stable202408-r1",
13+
"63a9217ddd51fa45d0a89fd83c483cc971765de6bb08e83cf70836b0baff0d48",
14+
),
15+
(
16+
"edk2-stable202408.01-r1",
17+
"1b4c7d7603517482a3c4461ba43044c4c7e0a7930274d77eb19600b7dcd9b838",
18+
),
19+
(
20+
"edk2-stable202411-r1",
21+
"963fc6cef6a0560cec97381ed22a7d5c76f440c8212529a034cb465466cd57cc",
22+
),
23+
(
24+
"edk2-stable202502-r1",
25+
"6d6122e88cdc09e1ffafb6a39fbdbfba668a6ded3f2a032b2cd6c0b7ff6d69df",
26+
),
27+
(
28+
"edk2-stable202502-r2",
29+
"dd59d3d52f0a643f07a488f80ab40f89c30f360999d98cdffb30e1eba5476641",
30+
),
31+
(
32+
"edk2-stable202505-r1",
33+
"7535021dc7f98453803cefcade59e839e9f559274493d681fec7b19219081e29",
34+
),
35+
];
36+
37+
pub fn update_sources() -> Result<()> {
38+
let sources = get_source_list()?;
39+
let path = Path::new("ovmf-prebuilt/src/source_constants.rs");
40+
update_rust_code_in_place(path, &sources)
41+
}
42+
43+
/// Corresponds to the `Source` type in `ovmf-prebuilt/src/lib.rs`.
44+
struct Source {
45+
tag: String,
46+
sha256: String,
47+
}
48+
49+
impl Source {
50+
fn ident(&self) -> String {
51+
self.tag.to_uppercase().replace(['-', '.'], "_")
52+
}
53+
}
54+
55+
/// Get the current list of sources.
56+
///
57+
/// This downloads the release list from Github, with really old
58+
/// releases excluded. For each release, the SHA-256 digest is retried
59+
/// from either the github API or from `OLD_RELEASES`. The output is
60+
/// sorted from oldest to newest.
61+
fn get_source_list() -> Result<Vec<Source>> {
62+
let github = Github::new();
63+
64+
let releases = github.get_releases()?;
65+
66+
let mut sources = Vec::new();
67+
68+
for release in &releases {
69+
// Exclude really old releases.
70+
if release.tag_name.as_str() < "edk2-stable202408-r1" {
71+
break;
72+
}
73+
74+
let sha256 = if let Ok(sha256) = release.sha256() {
75+
sha256
76+
} else if let Some(old_release) = OLD_RELEASES
77+
.iter()
78+
.find(|(name, _digest)| *name == release.tag_name.as_str())
79+
{
80+
old_release.1
81+
} else {
82+
bail!("missing SHA-256 digest for {}", release.tag_name);
83+
};
84+
85+
sources.push(Source {
86+
tag: release.tag_name.clone(),
87+
sha256: sha256.to_owned(),
88+
});
89+
}
90+
91+
sources.reverse();
92+
93+
Ok(sources)
94+
}
95+
96+
fn format_source_list(sources: &[Source]) -> String {
97+
let mut output = String::new();
98+
output += "use crate::Source;
99+
100+
#[allow(missing_docs)]
101+
impl Source {
102+
";
103+
for source in sources {
104+
output += &format!(
105+
" pub const {ident}: Self = Self {{
106+
tag: \"{tag}\",
107+
sha256: \"{sha256}\",
108+
}};
109+
110+
",
111+
ident = source.ident(),
112+
tag = source.tag,
113+
sha256 = source.sha256
114+
);
115+
}
116+
output += &format!(
117+
" /// Latest release tag.
118+
///
119+
/// Note that this is not necessarily the latest prebuilt available
120+
/// from the git repo.
121+
pub const LATEST: Self = Self::{latest};
122+
}}
123+
",
124+
latest = sources.last().unwrap().ident()
125+
);
126+
output
127+
}
128+
129+
fn update_rust_code_in_place(path: &Path, sources: &[Source]) -> Result<()> {
130+
let orig_contents = fs::read_to_string(path)?;
131+
let new_contents = format_source_list(sources);
132+
133+
if orig_contents == new_contents {
134+
println!("no changes");
135+
} else {
136+
println!("writing updates to {}", path.display());
137+
fs::write(path, new_contents)?;
138+
}
139+
Ok(())
140+
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
use super::*;
145+
146+
#[test]
147+
fn test_format_source_list() {
148+
let sources = [
149+
Source {
150+
tag: "tag-A".to_string(),
151+
sha256: "1234".to_string(),
152+
},
153+
Source {
154+
tag: "tag-B".to_string(),
155+
sha256: "5678".to_string(),
156+
},
157+
];
158+
159+
let expected = "use crate::Source;
160+
161+
#[allow(missing_docs)]
162+
impl Source {
163+
pub const TAG_A: Self = Self {
164+
tag: \"tag-A\",
165+
sha256: \"1234\",
166+
};
167+
168+
pub const TAG_B: Self = Self {
169+
tag: \"tag-B\",
170+
sha256: \"5678\",
171+
};
172+
173+
/// Latest release tag.
174+
///
175+
/// Note that this is not necessarily the latest prebuilt available
176+
/// from the git repo.
177+
pub const LATEST: Self = Self::TAG_B;
178+
}
179+
";
180+
181+
assert_eq!(format_source_list(&sources), expected);
182+
}
183+
}

0 commit comments

Comments
 (0)