Skip to content

Commit

Permalink
Merge branch 'main' into tools-ci
Browse files Browse the repository at this point in the history
  • Loading branch information
LiamGallagher737 committed Jul 11, 2024
2 parents 1b4b6ec + cd91e96 commit c470a6f
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 31 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/update-crates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Update Playground Crates

on:
workflow_dispatch:
schedule:
- cron: 0 0 * * *

jobs:
update-crates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/cache@v4
with:
path: |
~/tools/check_crate_updates/.cargo/bin/
~/tools/check_crate_updates/.cargo/registry/index/
~/tools/check_crate_updates/.cargo/registry/cache/
~/tools/check_crate_updates/.cargo/git/db/
~/tools/check_crate_updates/target/
key: ${{ runner.os }}-cargo-build-stable-${{ hashFiles('~/tools/check_crate_updates/Cargo.toml') }}

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal

- name: Build and run the Rust project
run: |
cd tools/check_crate_updates
cargo run ../../images/manifests
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.CRATE_UPDATER_APP_ID }}
private_key: ${{ secrets.CRATE_UPDATER_PRIVATE_KEY }}

- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.generate-token.outputs.token }}
commit-message: Update crates
title: Update Playground Crates
body: Update the 3rd party crates for the playground
branch: update-crates
base: main
labels: C-Crate-Update

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ devenv.local.nix

# pre-commit
.pre-commit-config.yaml

/tools/*/target
/tools/*/Cargo.lock
12 changes: 6 additions & 6 deletions images/manifests/0.10.Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ bevy = { version = "0.10", default-features = false, features = [
rand = "0.8"
rand_chacha = "0.3"

bevy_xpbd_2d = "0.1"
bevy_xpbd_3d = "0.1"
bevy_mod_picking = "0.14"
bevy_prototype_lyon = "0.8"
bevy_kira_audio = "0.15"
leafwing-input-manager = "0.9"
bevy_xpbd_2d = "=0.1.0"
bevy_xpbd_3d = "=0.1.0"
bevy_mod_picking = "=0.14.0"
bevy_prototype_lyon = "=0.8.0"
bevy_kira_audio = "=0.15.0"
leafwing-input-manager = "=0.9.3"

wasm-bindgen = "0.2"

Expand Down
12 changes: 6 additions & 6 deletions images/manifests/0.11.Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ bevy = { version = "0.11", default-features = false, features = [
rand = "0.8"
rand_chacha = "0.3"

bevy_xpbd_2d = "0.2"
bevy_xpbd_3d = "0.2"
bevy_mod_picking = "0.16"
bevy_prototype_lyon = "0.9"
bevy_kira_audio = "0.17"
leafwing-input-manager = "0.10"
bevy_xpbd_2d = "=0.2.0"
bevy_xpbd_3d = "=0.2.0"
bevy_mod_picking = "=0.16.0"
bevy_prototype_lyon = "=0.9.0"
bevy_kira_audio = "=0.17.0"
leafwing-input-manager = "=0.10.0"

wasm-bindgen = "0.2"

Expand Down
14 changes: 7 additions & 7 deletions images/manifests/0.12.Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ bevy = { version = "0.12", default-features = false, features = [
rand = "0.8"
rand_chacha = "0.3"

bevy_xpbd_2d = "0.3"
bevy_xpbd_3d = "0.3"
bevy_mod_picking = "0.17"
bevy_prototype_lyon = "0.10"
bevy_kira_audio = "0.18"
leafwing-input-manager = "0.12"
bevy_dev = "0.1"
bevy_xpbd_2d = "=0.3.3"
bevy_xpbd_3d = "=0.3.3"
bevy_mod_picking = "=0.17.1"
bevy_prototype_lyon = "=0.10.0"
bevy_kira_audio = "=0.18.0"
leafwing-input-manager = "=0.12.1"
bevy_dev = "=0.1.1"

wasm-bindgen = "0.2"

Expand Down
14 changes: 7 additions & 7 deletions images/manifests/0.13.Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ bevy = { version = "0.13", default-features = false, features = [
rand = "0.8.5"
rand_chacha = "0.3.1"

bevy_xpbd_2d = "0.4.2"
bevy_xpbd_3d = "0.4.2"
bevy_mod_picking = "0.18.2"
bevy_prototype_lyon = "0.11.0"
bevy_kira_audio = "0.19.0"
leafwing-input-manager = "0.13.3"
bevy_dev = { version = "0.3.0", default-features = false }
bevy_xpbd_2d = "=0.4.2"
bevy_xpbd_3d = "=0.4.2"
bevy_mod_picking = "=0.19.1"
bevy_prototype_lyon = "=0.11.0"
bevy_kira_audio = "=0.19.0"
leafwing-input-manager = "=0.13.3"
bevy_dev = { version = "=0.3.1", default-features = false }

wasm-bindgen = "0.2"

Expand Down
10 changes: 5 additions & 5 deletions images/manifests/0.14.Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ bevy = { version = "0.14", default-features = false, features = [
rand = "0.8.5"
rand_chacha = "0.3.1"

avian2d = "0.1.0"
avian3d = "0.1.0"
bevy_mod_picking = "0.20.0"
bevy_prototype_lyon = "0.12.0"
bevy_kira_audio = "0.20.0"
avian2d = "=0.1.0"
avian3d = "=0.1.0"
bevy_mod_picking = "=0.20.1"
bevy_prototype_lyon = "=0.12.0"
bevy_kira_audio = "=0.20.0"
#leafwing-input-manager = "0.13.3"
#bevy_dev = { version = "0.3.0", default-features = false }

Expand Down
13 changes: 13 additions & 0 deletions tools/check_crate_updates/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "check_crate_updates"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
semver = "1.0.23"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
table-extract = "0.2.3"
toml_edit = "0.22.15"
ureq = { version = "2.10.0", features = ["json"] }
206 changes: 206 additions & 0 deletions tools/check_crate_updates/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use anyhow::anyhow;
use std::{env, fs};
use table_extract::Table;
use ureq::{Agent, AgentBuilder};

const EXCLUDE_CRATES: &[&str] = &["bevy", "rand", "rand_chacha", "wasm-bindgen"];

fn main() -> anyhow::Result<()> {
let Some(manifest_dir) = env::args().nth(1) else {
return Err(anyhow!("A directory must be passed as an argument"));
};

let agent = AgentBuilder::new()
// Crates.io asks that you add a user agent with some way of contacting you
// https://crates.io/data-access#api
.user_agent("github.com/LiamGallagher737/learnbevy")
.build();

println!("Searching {manifest_dir}");

let manifest_paths = fs::read_dir(&manifest_dir)?
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| path.to_string_lossy().ends_with(".Cargo.toml"));

for path in manifest_paths {
let manifest_str = fs::read_to_string(&path)?;
let mut manifest = manifest_str
.parse::<toml_edit::DocumentMut>()
.map_err(|e| anyhow!("Failed to parse manifest at {path:?}\n{e}"))?;

// skip if no version - using main branch
if !manifest["dependencies"]["bevy"]
.as_inline_table()
.unwrap()
.contains_key("version")
{
println!("Skipping {path:?}");
continue;
}

let bevy_version = manifest["dependencies"]["bevy"]["version"]
.as_str()
.unwrap();

let crates = manifest["dependencies"]
.as_table()
.unwrap()
.iter()
.map(|(name, _)| name)
.filter(|name| !EXCLUDE_CRATES.contains(name))
.map(|name| fetch_crate(name, agent.clone()))
.inspect(|res| {
if let Err(e) = res {
eprintln!("Error getting crate: {e}");
}
})
.filter_map(|res| res.ok());

let mut newest_versions = Vec::new();

println!("Bevy: {bevy_version}");
for c in crates {
let readme = match fetch_readme(&c, agent.clone()) {
Ok(r) => r,
Err(e) => {
eprintln!("Error getting readme: {e}");
continue;
}
};

let table = match find_support_table(&readme) {
Ok(t) => t,
Err(e) => {
eprintln!("{e}");
continue;
}
};

// currently assuming the bevy column is first
let mut matching = Vec::new();
for row in table.iter().map(|r| r.as_slice()) {
let bevy = extract_version_from_cell(&row[0]);
let others = extract_versions_from_cell(&row[1]);
for other in others {
if bevy.starts_with(bevy_version) {
matching.push((bevy.clone(), other));
}
}
}

if matching.is_empty() {
eprintln!("{} has no matches for {bevy_version}", c.data.name);
continue;
}

let newest = matching
.iter()
.map(|(_, other)| other.parse::<semver::VersionReq>())
.inspect(|res| {
if let Err(e) = res {
eprintln!("Failed to parse: {e}");
}
})
.filter_map(Result::ok)
.map(|semver| {
c.versions
.iter()
.map(|v| v.version.parse::<semver::Version>().unwrap())
.filter(|v| semver.matches(v))
.max()
.unwrap()
})
.max()
.unwrap();

println!(
"The most recent version for {} compatible with Bevy {bevy_version} is {newest}",
c.data.name
);

newest_versions.push((c.data.name, format!("={newest}")));
}

for (name, version) in newest_versions {
if let Some(table) = manifest["dependencies"][&name].as_inline_table_mut() {
table["version"] = version.into();
} else {
manifest["dependencies"][name] = toml_edit::value(version);
}
}

if let Err(e) = fs::write(&path, manifest.to_string()) {
eprintln!("Failed to write to {path:?}: {e}");
}
}

println!("Complete");

Ok(())
}

fn fetch_crate(name: &str, agent: Agent) -> anyhow::Result<CrateResponse> {
agent
.get(&format!("https://crates.io/api/v1/crates/{name}"))
.call()
.map_err(|e| anyhow!("Failed to fetch crate data for {name:?}\n{e}"))?
.into_json::<CrateResponse>()
.map_err(|e| anyhow!("Failed to parse crate data for {name:?}\n{e}"))
}

fn fetch_readme(c: &CrateResponse, agent: Agent) -> anyhow::Result<String> {
let path = &c.versions[0].readme_path; // index 0 is latest
agent
.get(&format!("https://crates.io{path}"))
.call()
.map_err(|e| anyhow!("Failed to fetch readme\n{e}"))?
.into_string()
.map_err(|e| anyhow!("Failed to read readme\n{e}"))
}

fn find_support_table(readme: &str) -> anyhow::Result<Table> {
const BEVY_HEADER_PHRASES: &[&str] = &["bevy", "Bevy", "Bevy version"];
let find = |phrase: &str| Table::find_by_headers(readme, &[phrase]);
for phrase in BEVY_HEADER_PHRASES {
if let Some(table) = find(phrase) {
return Ok(table);
}
}
Err(anyhow!("Failed to find support table in readme"))
}

fn extract_version_from_cell(input: &str) -> String {
input
.chars()
.filter(|&c| c.is_ascii_digit() || c == '.')
.collect()
}

fn extract_versions_from_cell(input: &str) -> Vec<String> {
input
.split([' ', ',', '-'])
.flat_map(|s| s.split(".."))
.map(extract_version_from_cell)
.filter(|s| !s.is_empty())
.collect()
}

#[derive(serde::Deserialize)]
struct CrateResponse {
#[serde(rename = "crate")]
data: CrateDataResponse,
versions: Vec<CrateVersionResponse>,
}

#[derive(serde::Deserialize)]
struct CrateDataResponse {
name: String,
}

#[derive(serde::Deserialize)]
struct CrateVersionResponse {
#[serde(rename = "num")]
version: String,
readme_path: String,
}

0 comments on commit c470a6f

Please sign in to comment.