diff --git a/tools/Cargo.lock b/tools/Cargo.lock index 117ad0a..4e201ae 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -25,6 +25,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + [[package]] name = "clap" version = "3.2.16" @@ -64,6 +70,22 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "combine" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + [[package]] name = "hashbrown" version = "0.12.3" @@ -95,12 +117,27 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "libc" version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "once_cell" version = "1.13.0" @@ -187,11 +224,23 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +[[package]] +name = "toml_edit" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5376256e44f2443f8896ac012507c19a012df0fe8758b55246ae51a2279db51f" +dependencies = [ + "combine", + "indexmap", + "itertools", +] + [[package]] name = "tools" version = "0.1.0" dependencies = [ "clap", + "toml_edit", ] [[package]] diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 69994b5..2cf6755 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] clap = { version = "3.2.16", features = ["derive", "cargo"] } +toml_edit = "0.14.4" diff --git a/tools/src/main.rs b/tools/src/main.rs index 50d4ed8..5c1ed24 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -13,8 +13,12 @@ fn main() { .subcommand(clap::Command::new("publish").about("Publishes agb and all subcrates")) .get_matches(); - match matches.subcommand() { + let result = match matches.subcommand() { Some(("publish", _)) => publish::publish(), _ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"), + }; + + if let Err(e) = result { + eprintln!("Error: {:?}", e); } } diff --git a/tools/src/publish.rs b/tools/src/publish.rs index d3fb88a..06e9980 100644 --- a/tools/src/publish.rs +++ b/tools/src/publish.rs @@ -1,3 +1,135 @@ -pub fn publish() { - println!("Publish!"); +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Duration; +use std::{env, thread}; +use toml_edit::Document; + +const CRATES_TO_PUBLISH: &[&str] = &[ + "agb-macros", + "agb-fixnum", + "agb-image-converter", + "agb-sound-converter", + "agb", +]; + +#[derive(Debug)] +pub enum Error { + FindRootDirectory, + PublishCrate, + Poll, + CrateVersion, +} + +pub fn publish() -> Result<(), Error> { + let root_directory = find_agb_root_directory()?; + + for crate_to_publish in CRATES_TO_PUBLISH.iter() { + let crate_dir = root_directory.join(crate_to_publish); + let publish_result = Command::new("cargo") + .arg("publish") + .current_dir(&crate_dir) + .spawn(); + + if let Err(err) = publish_result { + println!("Error while publishing crate {crate_to_publish}: {err}"); + return Err(Error::PublishCrate); + } + + let expected_version = read_cargo_toml_version(&crate_dir)?; + wait_for_release(crate_to_publish, &expected_version)?; + } + + Ok(()) +} + +fn find_agb_root_directory() -> Result { + let mut current_path = env::current_dir().map_err(|_| Error::FindRootDirectory)?; + + while !current_path.clone().join("justfile").exists() { + current_path = current_path + .parent() + .ok_or(Error::FindRootDirectory)? + .to_owned(); + } + + Ok(current_path) +} + +fn wait_for_release(crate_to_publish: &str, expected_version: &str) -> Result<(), Error> { + let url_to_poll = &get_url_to_poll(crate_to_publish); + + for attempt in 0..15 { + println!( + "Polling crates.io with URL {url_to_poll} for {crate_to_publish} hoping for version {expected_version}. Attempt {attempt}" + ); + + let curl_result = Command::new("curl") + .arg(url_to_poll) + .output() + .map_err(|_| Error::Poll)?; + + if String::from_utf8_lossy(&curl_result.stdout).contains(expected_version) { + return Ok(()); + } + + thread::sleep(Duration::from_secs(30)); + } + + Ok(()) +} + +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 { + let cargo_toml_contents = + fs::read_to_string(folder.join("Cargo.toml")).map_err(|_| Error::CrateVersion)?; + let cargo_toml: Document = cargo_toml_contents + .parse() + .map_err(|_| Error::CrateVersion)?; + + let version_value = cargo_toml["package"]["version"] + .as_value() + .ok_or(Error::CrateVersion)? + .as_str() + .ok_or(Error::CrateVersion)?; + + Ok(version_value.to_owned()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn url_to_poll_should_return_correct_url() { + let test_cases = [ + ["agb", "3/a/agb"], + ["agb-image-converter", "ag/b_/agb_image_converter"], + ["agb-fixnum", "ag/b_/agb_fixnum"], + ]; + + for [name, result] in test_cases { + let url = get_url_to_poll(name); + assert_eq!( + url, + format!( + "https://raw.githubusercontent.com/rust-lang/crates.io-index/master/{result}", + ) + ) + } + } }