Make publishing a bit smarter

This commit is contained in:
Gwilym Inzani 2023-04-11 22:29:23 +01:00
parent 70979fc7da
commit 63995995ac
3 changed files with 101 additions and 98 deletions

26
tools/Cargo.lock generated
View file

@ -138,6 +138,15 @@ dependencies = [
"syn 2.0.8", "syn 2.0.8",
] ]
[[package]]
name = "dependency-graph"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5143247629540606d0888beae9ca0e0b9a81a32151bfecd0b2be4a961155c24d"
dependencies = [
"petgraph",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.2.8" version = "0.2.8"
@ -159,6 +168,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.1" version = "0.3.1"
@ -310,6 +325,16 @@ version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
[[package]]
name = "petgraph"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.53" version = "1.0.53"
@ -419,6 +444,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
"dependency-graph",
"glob", "glob",
"toml_edit", "toml_edit",
] ]

View file

@ -10,3 +10,4 @@ clap = "4"
toml_edit = "0.19" toml_edit = "0.19"
glob = "0.3" glob = "0.3"
chrono = "0.4" chrono = "0.4"
dependency-graph = "0.1.5"

View file

@ -1,10 +1,11 @@
use clap::{Arg, ArgAction, ArgMatches}; use clap::{Arg, ArgAction, ArgMatches};
use std::collections::{HashMap, HashSet}; use dependency_graph::DependencyGraph;
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use std::thread;
use std::time::Duration;
use toml_edit::Document; use toml_edit::Document;
use crate::utils::*; use crate::utils::*;
@ -19,6 +20,23 @@ pub enum Error {
CargoToml, CargoToml,
} }
struct Package {
name: String,
dependencies: Vec<String>,
}
impl dependency_graph::Node for Package {
type DependencyType = String;
fn dependencies(&self) -> &[Self::DependencyType] {
&self.dependencies
}
fn matches(&self, dependency: &Self::DependencyType) -> bool {
&self.name == dependency
}
}
pub fn command() -> clap::Command { pub fn command() -> clap::Command {
clap::Command::new("publish") clap::Command::new("publish")
.about("Publishes agb and all subcrates") .about("Publishes agb and all subcrates")
@ -32,96 +50,51 @@ pub fn command() -> clap::Command {
pub fn publish(matches: &ArgMatches) -> Result<(), Error> { pub fn publish(matches: &ArgMatches) -> Result<(), Error> {
let dry_run = matches.get_one::<bool>("Dry run").expect("defined by clap"); let dry_run = matches.get_one::<bool>("Dry run").expect("defined by clap");
let dry_run = if *dry_run { vec!["--dry-run"] } else { vec![] };
let root_directory = find_agb_root_directory().map_err(|_| Error::FindRootDirectory)?; let root_directory = find_agb_root_directory().map_err(|_| Error::FindRootDirectory)?;
let mut fully_published_crates: HashSet<String> = HashSet::new(); let mut in_progress: HashMap<_, RefCell<std::process::Child>> = HashMap::new();
let mut published_crates: HashSet<String> = HashSet::new();
let dependencies = build_dependency_graph(&root_directory)?; let dependencies = build_dependency_graph(&root_directory)?;
let mut crates_to_publish: Vec<_> = dependencies.keys().collect(); let graph = DependencyGraph::from(&dependencies[..]);
let agb_gbafix = "agb-gbafix".to_owned();
crates_to_publish.push(&agb_gbafix);
while published_crates.len() != crates_to_publish.len() { for package in graph {
// find all crates which can be published now but haven't let package = package.as_resolved().unwrap();
let publishable_crates: Vec<_> = crates_to_publish
.iter()
.filter(|&&crate_to_publish| !published_crates.contains(crate_to_publish))
.filter(|&&crate_to_publish| {
let dependencies_of_crate = &dependencies.get(crate_to_publish);
if let Some(dependencies_of_crate) = dependencies_of_crate { for dep in &package.dependencies {
for dependency_of_crate in *dependencies_of_crate { assert!(in_progress
if !fully_published_crates.contains(dependency_of_crate) { .get(dep)
return false; .unwrap()
} .borrow_mut()
} .wait()
}
true
})
.collect();
for publishable_crate in publishable_crates {
if *dry_run {
println!("Would execute cargo publish for {publishable_crate}");
} else {
assert!(Command::new("cargo")
.arg("publish")
.current_dir(&root_directory.join(publishable_crate))
.status()
.map_err(|_| Error::PublishCrate)? .map_err(|_| Error::PublishCrate)?
.success()); .success());
} }
published_crates.insert(publishable_crate.to_string()); println!("Publishing {}", package.name);
let publish_cmd = Command::new("cargo")
.arg("publish")
.args(&dry_run)
.current_dir(root_directory.join(&package.name))
.spawn()
.map_err(|_| Error::PublishCrate)?;
in_progress.insert(package.name.clone(), RefCell::new(publish_cmd));
} }
for published_crate in published_crates.iter() { for (_, in_progress) in in_progress {
if !fully_published_crates.contains(published_crate) { assert!(in_progress
let expected_version = .borrow_mut()
read_cargo_toml_version(&root_directory.join(published_crate))?; .wait()
if check_if_released(published_crate, &expected_version)? { .map_err(|_| Error::PublishCrate)?
fully_published_crates.insert(published_crate.clone()); .success());
}
}
}
thread::sleep(Duration::from_secs(10));
} }
Ok(()) Ok(())
} }
fn check_if_released(crate_to_publish: &str, expected_version: &str) -> Result<bool, Error> {
let url_to_poll = &get_url_to_poll(crate_to_publish);
println!("Polling crates.io with URL {url_to_poll} for {crate_to_publish} hoping for version {expected_version}.");
let curl_result = Command::new("curl")
.arg(url_to_poll)
.output()
.map_err(|_| Error::Poll)?;
Ok(String::from_utf8_lossy(&curl_result.stdout).contains(expected_version))
}
fn get_url_to_poll(crate_name: &str) -> String {
let crate_name_with_underscores = crate_name.replace('-', "_");
let crate_folder = if crate_name_with_underscores.len() == 3 {
format!("3/{}", crate_name_with_underscores.chars().next().unwrap())
} else {
let first_two_characters = &crate_name_with_underscores[0..2];
let second_two_characters = &crate_name_with_underscores[2..4];
format!("{first_two_characters}/{second_two_characters}")
};
format!("https://raw.githubusercontent.com/rust-lang/crates.io-index/master/{crate_folder}/{crate_name_with_underscores}")
}
fn read_cargo_toml_version(folder: &Path) -> Result<String, Error> { fn read_cargo_toml_version(folder: &Path) -> Result<String, Error> {
let cargo_toml = read_cargo_toml(folder)?; let cargo_toml = read_cargo_toml(folder)?;
@ -134,30 +107,33 @@ fn read_cargo_toml_version(folder: &Path) -> Result<String, Error> {
Ok(version_value.to_owned()) Ok(version_value.to_owned())
} }
fn build_dependency_graph(root: &Path) -> Result<HashMap<String, Vec<String>>, Error> { fn build_dependency_graph(root: &Path) -> Result<Vec<Package>, Error> {
let mut result = HashMap::new(); let dirs = fs::read_dir(root).map_err(|_| Error::ReadingDependencies)?;
result.insert("agb".to_owned(), get_agb_dependencies(&root.join("agb"))?); let mut packages = vec![];
let mut added_new_crates = true; for dir in dirs {
while added_new_crates { let dir = dir.map_err(|_| Error::ReadingDependencies)?;
added_new_crates = false; if !dir
.file_type()
let all_crates: HashSet<String> = HashSet::from_iter(result.values().flatten().cloned()); .map_err(|_| Error::ReadingDependencies)?
.is_dir()
for dep_crate in all_crates { {
if result.contains_key(&dep_crate) {
continue; continue;
} }
added_new_crates = true; if !dir.file_name().to_string_lossy().starts_with("agb") {
result.insert( continue;
dep_crate.to_owned(),
get_agb_dependencies(&root.join(dep_crate))?,
);
}
} }
Ok(result) let crate_path = root.join(dir.path());
packages.push(Package {
name: dir.file_name().to_string_lossy().to_string(),
dependencies: get_agb_dependencies(&crate_path)?,
});
}
Ok(packages)
} }
fn get_agb_dependencies(folder: &Path) -> Result<Vec<String>, Error> { fn get_agb_dependencies(folder: &Path) -> Result<Vec<String>, Error> {