|
8 | 8 | octocrab::OctocrabBuilder,
|
9 | 9 | once_cell::sync::Lazy,
|
10 | 10 | serde::Deserialize,
|
11 |
| - std::{collections::HashMap, io::Read, path::PathBuf}, |
| 11 | + std::{ |
| 12 | + collections::{BTreeMap, BTreeSet}, |
| 13 | + io::Read, |
| 14 | + path::PathBuf, |
| 15 | + }, |
12 | 16 | zip::ZipArchive,
|
13 | 17 | };
|
14 | 18 |
|
15 |
| -static SUFFIXES_BY_TRIPLE: Lazy<HashMap<&'static str, Vec<&'static str>>> = Lazy::new(|| { |
16 |
| - let mut h = HashMap::new(); |
| 19 | +static SUFFIXES_BY_TRIPLE: Lazy<BTreeMap<&'static str, Vec<&'static str>>> = Lazy::new(|| { |
| 20 | + let mut h = BTreeMap::new(); |
17 | 21 |
|
18 | 22 | // macOS.
|
19 | 23 | let macos_suffixes = vec!["debug", "lto", "pgo", "pgo+lto", "install_only"];
|
@@ -168,3 +172,115 @@ pub async fn command_fetch_release_distributions(args: &ArgMatches<'_>) -> Resul
|
168 | 172 |
|
169 | 173 | Ok(())
|
170 | 174 | }
|
| 175 | + |
| 176 | +pub async fn command_upload_release_distributions(args: &ArgMatches<'_>) -> Result<()> { |
| 177 | + let dist_dir = PathBuf::from(args.value_of("dist").expect("dist should be specified")); |
| 178 | + let datetime = args |
| 179 | + .value_of("datetime") |
| 180 | + .expect("datetime should be specified"); |
| 181 | + let tag = args.value_of("tag").expect("tag should be specified"); |
| 182 | + let ignore_missing = args.is_present("ignore_missing"); |
| 183 | + let token = args |
| 184 | + .value_of("token") |
| 185 | + .expect("token should be specified") |
| 186 | + .to_string(); |
| 187 | + let organization = args |
| 188 | + .value_of("organization") |
| 189 | + .expect("organization should be specified"); |
| 190 | + let repo = args.value_of("repo").expect("repo should be specified"); |
| 191 | + |
| 192 | + let mut filenames = std::fs::read_dir(&dist_dir)? |
| 193 | + .into_iter() |
| 194 | + .map(|x| { |
| 195 | + let path = x?.path(); |
| 196 | + let filename = path |
| 197 | + .file_name() |
| 198 | + .ok_or_else(|| anyhow!("unable to resolve file name"))?; |
| 199 | + |
| 200 | + Ok(filename.to_string_lossy().to_string()) |
| 201 | + }) |
| 202 | + .collect::<Result<Vec<_>>>()?; |
| 203 | + filenames.sort(); |
| 204 | + |
| 205 | + let filenames = filenames |
| 206 | + .into_iter() |
| 207 | + .filter(|x| x.contains(datetime) && x.starts_with("cpython-")) |
| 208 | + .collect::<BTreeSet<_>>(); |
| 209 | + |
| 210 | + let mut python_versions = BTreeSet::new(); |
| 211 | + for filename in &filenames { |
| 212 | + let parts = filename.split('-').collect::<Vec<_>>(); |
| 213 | + python_versions.insert(parts[1]); |
| 214 | + } |
| 215 | + |
| 216 | + let mut wanted_filenames = BTreeSet::new(); |
| 217 | + for version in python_versions { |
| 218 | + for (triple, suffixes) in SUFFIXES_BY_TRIPLE.iter() { |
| 219 | + for suffix in suffixes { |
| 220 | + let extension = if suffix.contains("install_only") { |
| 221 | + "tar.gz" |
| 222 | + } else { |
| 223 | + "tar.zst" |
| 224 | + }; |
| 225 | + |
| 226 | + wanted_filenames.insert(format!( |
| 227 | + "cpython-{}-{}-{}-{}.{}", |
| 228 | + version, triple, suffix, datetime, extension |
| 229 | + )); |
| 230 | + } |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + let missing = wanted_filenames.difference(&filenames).collect::<Vec<_>>(); |
| 235 | + for f in &missing { |
| 236 | + println!("missing release artifact: {}", f); |
| 237 | + } |
| 238 | + if !missing.is_empty() && !ignore_missing { |
| 239 | + return Err(anyhow!("missing release artifacts")); |
| 240 | + } |
| 241 | + |
| 242 | + let client = OctocrabBuilder::new().personal_token(token).build()?; |
| 243 | + let repo = client.repos(organization, repo); |
| 244 | + let releases = repo.releases(); |
| 245 | + |
| 246 | + let release = if let Ok(release) = releases.get_by_tag(tag).await { |
| 247 | + release |
| 248 | + } else { |
| 249 | + return Err(anyhow!( |
| 250 | + "release {} does not exist; create it via GitHub web UI", |
| 251 | + tag |
| 252 | + )); |
| 253 | + }; |
| 254 | + |
| 255 | + for filename in wanted_filenames.intersection(&filenames) { |
| 256 | + let path = dist_dir.join(filename); |
| 257 | + let file_data = std::fs::read(&path)?; |
| 258 | + |
| 259 | + let mut url = release.upload_url.clone(); |
| 260 | + let path = url.path().to_string(); |
| 261 | + |
| 262 | + if let Some(path) = path.strip_suffix("%7B") { |
| 263 | + url.set_path(path); |
| 264 | + } |
| 265 | + |
| 266 | + url.query_pairs_mut() |
| 267 | + .clear() |
| 268 | + .append_pair("name", filename.as_str()); |
| 269 | + |
| 270 | + println!("uploading {} to {}", filename, url); |
| 271 | + |
| 272 | + let request = client |
| 273 | + .request_builder(url, reqwest::Method::POST) |
| 274 | + .header("Content-Length", file_data.len()) |
| 275 | + .header("Content-Type", "application/x-tar") |
| 276 | + .body(file_data); |
| 277 | + |
| 278 | + let response = client.execute(request).await?; |
| 279 | + |
| 280 | + if !response.status().is_success() { |
| 281 | + return Err(anyhow!("HTTP {}", response.status())); |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + Ok(()) |
| 286 | +} |
0 commit comments