From fc68f351ed9e87eb2b00e803682782095da731e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 6 May 2023 16:54:26 +0200 Subject: [PATCH 1/6] new example showcase tool --- Cargo.toml | 3 +- tools/example-showcase/Cargo.toml | 13 ++ tools/example-showcase/src/main.rs | 331 +++++++++++++++++++++++++++++ tools/example_showcase.sh | 18 -- 4 files changed, 346 insertions(+), 19 deletions(-) create mode 100644 tools/example-showcase/Cargo.toml create mode 100644 tools/example-showcase/src/main.rs delete mode 100644 tools/example_showcase.sh diff --git a/Cargo.toml b/Cargo.toml index f7b0eb6f3986d..88b42749dbc08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "tools/ci", "tools/build-templated-pages", "tools/build-wasm-example", + "tools/example-showcase", "errors", ] @@ -1673,7 +1674,7 @@ path = "examples/stress_tests/transform_hierarchy.rs" name = "Transform Hierarchy" description = "Various test cases for hierarchy and transform propagation performance" category = "Stress Tests" -wasm = true +wasm = false [[example]] name = "text_pipeline" diff --git a/tools/example-showcase/Cargo.toml b/tools/example-showcase/Cargo.toml new file mode 100644 index 0000000000000..cc7661cb89b0f --- /dev/null +++ b/tools/example-showcase/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example-showcase" +version = "0.1.0" +edition = "2021" +description = "Run examples" +publish = false +license = "MIT OR Apache-2.0" + +[dependencies] +xshell = "0.2" +clap = { version = "4.0", features = ["derive"] } +ron = "0.8" +toml_edit = "0.19" diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs new file mode 100644 index 0000000000000..ec372592a94f2 --- /dev/null +++ b/tools/example-showcase/src/main.rs @@ -0,0 +1,331 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::Write, + path::{Path, PathBuf}, + process::exit, + thread, + time::{Duration, Instant}, +}; + +use clap::Parser; +use toml_edit::Document; +use xshell::{cmd, Shell}; + +#[derive(Parser, Debug)] +struct Args { + #[arg(long, default_value = "release")] + /// Compilation profile to use + profile: String, + + #[command(subcommand)] + action: Action, +} + +#[derive(clap::Subcommand, Debug)] +enum Action { + /// Run all the examples + Run { + #[arg(long)] + /// WGPU backend to use + wgpu_backend: Option, + + #[arg(long)] + /// Don't stop automatically + manual_stop: bool, + + #[arg(long)] + /// Take a screenshot + screenshot: bool, + }, + /// Build the markdown files for the website + BuildWebsiteList { + #[arg(long)] + /// Path to the folder where the content should be created + content_folder: String, + }, + /// BUild the examples in wasm / WebGPU + BuildWebGPUExamples { + #[arg(long)] + /// Path to the folder where the content should be created + content_folder: String, + }, +} + +fn main() { + let cli = Args::parse(); + + let profile = cli.profile; + + match cli.action { + Action::Run { + wgpu_backend, + manual_stop, + screenshot, + } => { + let examples_to_run = parse_examples(); + + let mut failed_examples = vec![]; + + let mut extra_parameters = vec![]; + + match (manual_stop, screenshot) { + (true, true) => { + let mut file = File::create("example_showcase_config.ron").unwrap(); + file.write_all( + b"(exit_after: None, frame_time: Some(0.05), screenshot_frames: [100])", + ) + .unwrap(); + extra_parameters.push("--features"); + extra_parameters.push("bevy_ci_testing"); + } + (true, false) => (), + (false, true) => { + let mut file = File::create("example_showcase_config.ron").unwrap(); + file.write_all( + b"(exit_after: Some(300), frame_time: Some(0.05), screenshot_frames: [100])", + ) + .unwrap(); + extra_parameters.push("--features"); + extra_parameters.push("bevy_ci_testing"); + } + (false, false) => { + let mut file = File::create("example_showcase_config.ron").unwrap(); + file.write_all(b"(exit_after: Some(300))").unwrap(); + extra_parameters.push("--features"); + extra_parameters.push("bevy_ci_testing"); + } + } + + for to_run in examples_to_run { + let sh = Shell::new().unwrap(); + let example = &to_run.technical_name; + let extra_parameters = extra_parameters.clone(); + let mut cmd = cmd!( + sh, + "cargo run --profile {profile} --example {example} {extra_parameters...}" + ); + + if let Some(backend) = wgpu_backend.as_ref() { + cmd = cmd.env("WGPU_BACKEND", backend); + } + + if !manual_stop { + cmd = cmd.env("CI_TESTING_CONFIG", format!("example_showcase_config.ron")); + } + + let before = Instant::now(); + + if cmd.run().is_ok() { + if screenshot { + let _ = fs::create_dir_all(Path::new("screenshots").join(&to_run.category)); + let _ = fs::rename( + "screenshot-100.png", + Path::new("screenshots") + .join(&to_run.category) + .join(format!("{}.png", to_run.technical_name)), + ); + } + } else { + failed_examples.push(to_run); + } + + let duration = before.elapsed(); + println!("took {duration:?}"); + + thread::sleep(Duration::from_secs(1)); + } + if failed_examples.is_empty() { + println!("All examples passed!"); + } else { + println!("Failed examples:"); + for example in failed_examples { + println!( + " {} / {} ({})", + example.category, example.name, example.technical_name + ); + } + exit(1); + } + } + Action::BuildWebsiteList { content_folder } => { + let examples_to_run = parse_examples(); + + let root_path = Path::new(&content_folder); + + let _ = fs::create_dir_all(&root_path); + + let mut index = File::create(root_path.join("_index.md")).unwrap(); + index + .write_all( + "+++ +title = \"Bevy Examples in WebGPU\" +template = \"examples-webgpu.html\" +sort_by = \"weight\" + +[extra] +header_message = \"Examples (WebGPU)\" ++++" + .as_bytes(), + ) + .unwrap(); + + let mut categories = HashMap::new(); + + for to_show in examples_to_run { + if !to_show.wasm { + continue; + } + let category_path = root_path.join(&to_show.category); + + if !categories.contains_key(&to_show.category) { + let _ = fs::create_dir_all(&category_path); + let mut category_index = File::create(category_path.join("_index.md")).unwrap(); + category_index + .write_all( + format!( + "+++ +title = \"{}\" +sort_by = \"weight\" +weight = {} ++++", + to_show.category, + categories.len() + ) + .as_bytes(), + ) + .unwrap(); + categories.insert(to_show.category.clone(), 0); + } + let example_path = category_path.join(&to_show.technical_name.replace('_', "-")); + let _ = fs::create_dir_all(&example_path); + + let code_path = example_path.join(Path::new(&to_show.path).file_name().unwrap()); + let _ = fs::copy(&to_show.path, &code_path); + + let mut example_index = File::create(example_path.join("index.md")).unwrap(); + example_index + .write_all( + format!( + "+++ +title = \"{}\" +template = \"example-webgpu.html\" +weight = {} +description = \"{}\" + +[extra] +technical_name = \"{}\" +link = \"examples-webgpu/{}/{}\" +image = \"../static/screenshots/{}/{}.png\" +code_path = \"content/examples-webgpu/{}\" +github_code_path = \"examples/$category_path/$code_filename\" +header_message = \"Examples (WebGPU)\" ++++", + to_show.name, + categories.get(&to_show.category).unwrap(), + to_show.description.replace('"', "'"), + &to_show.technical_name.replace('_', "-"), + &to_show.category, + &to_show.technical_name.replace('_', "-"), + &to_show.category, + &to_show.technical_name, + code_path + .components() + .skip(1) + .collect::() + .display() + ) + .as_bytes(), + ) + .unwrap(); + } + } + Action::BuildWebGPUExamples { content_folder } => { + let examples_to_build = parse_examples(); + + let root_path = Path::new(&content_folder); + + let _ = fs::create_dir_all(&root_path); + + for to_build in examples_to_build { + if !to_build.wasm { + continue; + } + + let sh = Shell::new().unwrap(); + let example = &to_build.technical_name; + cmd!( + sh, + "cargo run -p build-wasm-example -- --api webgpu {example}" + ) + .run() + .unwrap(); + + let category_path = root_path.join(&to_build.category); + let _ = fs::create_dir_all(&category_path); + + let example_path = category_path.join(&to_build.technical_name.replace('_', "-")); + let _ = fs::create_dir_all(&example_path); + + let _ = fs::rename( + Path::new("examples/wasm/target/wasm_example.js"), + &example_path.join("wasm_example.js"), + ); + let _ = fs::rename( + Path::new("examples/wasm/target/wasm_example_bg.wasm"), + &example_path.join("wasm_example_bg.wasm"), + ); + } + } + } +} + +fn parse_examples() -> Vec { + let manifest_file = std::fs::read_to_string("Cargo.toml").unwrap(); + let manifest = manifest_file.parse::().unwrap(); + let metadatas = manifest + .get("package") + .unwrap() + .get("metadata") + .as_ref() + .unwrap()["example"] + .clone(); + + manifest["example"] + .as_array_of_tables() + .unwrap() + .iter() + .flat_map(|val| { + let technical_name = val.get("name").unwrap().as_str().unwrap().to_string(); + + if metadatas + .get(&technical_name) + .and_then(|metadata| metadata.get("hidden")) + .and_then(|hidden| hidden.as_bool()) + .and_then(|hidden| hidden.then_some(())) + .is_some() + { + return None; + } + + metadatas.get(&technical_name).map(|metadata| Example { + technical_name, + path: val["path"].as_str().unwrap().to_string(), + name: metadata["name"].as_str().unwrap().to_string(), + description: metadata["description"].as_str().unwrap().to_string(), + category: metadata["category"].as_str().unwrap().to_string(), + wasm: metadata["wasm"].as_bool().unwrap(), + }) + }) + .collect() +} + +#[derive(Debug, PartialEq, Eq, Clone)] +struct Example { + technical_name: String, + path: String, + name: String, + description: String, + category: String, + wasm: bool, +} diff --git a/tools/example_showcase.sh b/tools/example_showcase.sh deleted file mode 100644 index 096d0b1d6c252..0000000000000 --- a/tools/example_showcase.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -duration='2' -function wait_seconds() { perl -e 'alarm shift; exec @ARGV' "$@"; } -run_example() { - cargo build --example "$1" - wait_seconds "$duration" cargo run --example "$1" -} - -example_list="$(cargo build --example 2>&1)" -example_list=${example_list//$'\n'/} -example_list="${example_list#error\: \"--example\" takes one argument.Available examples\: }" -echo "$example_list" -for example in $example_list -do - echo "Running example: $example" - run_example "$example" -done From ee27fea1732efc480a7040af705acc41db52972a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 6 May 2023 17:45:23 +0200 Subject: [PATCH 2/6] clippy --- tools/example-showcase/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs index ec372592a94f2..d47ab48b258ab 100644 --- a/tools/example-showcase/src/main.rs +++ b/tools/example-showcase/src/main.rs @@ -111,7 +111,7 @@ fn main() { } if !manual_stop { - cmd = cmd.env("CI_TESTING_CONFIG", format!("example_showcase_config.ron")); + cmd = cmd.env("CI_TESTING_CONFIG", "example_showcase_config.ron".to_string()); } let before = Instant::now(); @@ -153,7 +153,7 @@ fn main() { let root_path = Path::new(&content_folder); - let _ = fs::create_dir_all(&root_path); + let _ = fs::create_dir_all(root_path); let mut index = File::create(root_path.join("_index.md")).unwrap(); index @@ -245,7 +245,7 @@ header_message = \"Examples (WebGPU)\" let root_path = Path::new(&content_folder); - let _ = fs::create_dir_all(&root_path); + let _ = fs::create_dir_all(root_path); for to_build in examples_to_build { if !to_build.wasm { From 2f57c545c99e039c50a397ae3329063b4eda8017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 6 May 2023 18:05:49 +0200 Subject: [PATCH 3/6] format --- tools/example-showcase/src/main.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs index d47ab48b258ab..fe812e148c2ec 100644 --- a/tools/example-showcase/src/main.rs +++ b/tools/example-showcase/src/main.rs @@ -110,8 +110,11 @@ fn main() { cmd = cmd.env("WGPU_BACKEND", backend); } - if !manual_stop { - cmd = cmd.env("CI_TESTING_CONFIG", "example_showcase_config.ron".to_string()); + if !manual_stop || screenshot { + cmd = cmd.env( + "CI_TESTING_CONFIG", + "example_showcase_config.ron".to_string(), + ); } let before = Instant::now(); From 32b63edb31c250dc1782b821ee0c3a9751041309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 6 May 2023 18:30:18 +0200 Subject: [PATCH 4/6] more clippy --- tools/example-showcase/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs index fe812e148c2ec..6415a9653fdc8 100644 --- a/tools/example-showcase/src/main.rs +++ b/tools/example-showcase/src/main.rs @@ -111,10 +111,7 @@ fn main() { } if !manual_stop || screenshot { - cmd = cmd.env( - "CI_TESTING_CONFIG", - "example_showcase_config.ron".to_string(), - ); + cmd = cmd.env("CI_TESTING_CONFIG", "example_showcase_config.ron"); } let before = Instant::now(); From 4e9e876e3fadd91ebab9360bfd88af608262e0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 6 May 2023 21:39:40 +0200 Subject: [PATCH 5/6] add website hacks for building wasm --- tools/example-showcase/src/main.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs index 6415a9653fdc8..ca68073e5641d 100644 --- a/tools/example-showcase/src/main.rs +++ b/tools/example-showcase/src/main.rs @@ -49,6 +49,10 @@ enum Action { #[arg(long)] /// Path to the folder where the content should be created content_folder: String, + + #[arg(long)] + /// Enable hacks for Bevy website integration + website_hacks: bool, }, } @@ -240,13 +244,27 @@ header_message = \"Examples (WebGPU)\" .unwrap(); } } - Action::BuildWebGPUExamples { content_folder } => { + Action::BuildWebGPUExamples { + content_folder, + website_hacks, + } => { let examples_to_build = parse_examples(); let root_path = Path::new(&content_folder); let _ = fs::create_dir_all(root_path); + if website_hacks { + let sh = Shell::new().unwrap(); + + // setting a canvas by default to help with integration + cmd!(sh, "sed -i.bak 's/canvas: None,/canvas: Some(\"#bevy\".to_string()),/' crates/bevy_window/src/window.rs").run().unwrap(); + cmd!(sh, "sed -i.bak 's/fit_canvas_to_parent: false,/fit_canvas_to_parent: true,/' crates/bevy_window/src/window.rs").run().unwrap(); + + // setting the asset folder root to the root url of this domain + cmd!(sh, "sed -i.bak 's/asset_folder: \"assets\"/asset_folder: \"\\/assets\\/examples\\/\"/' crates/bevy_asset/src/lib.rs").run().unwrap(); + } + for to_build in examples_to_build { if !to_build.wasm { continue; @@ -267,6 +285,11 @@ header_message = \"Examples (WebGPU)\" let example_path = category_path.join(&to_build.technical_name.replace('_', "-")); let _ = fs::create_dir_all(&example_path); + if website_hacks { + // set up the loader bar for asset loading + cmd!(sh, "sed -i.bak -e 's/getObject(arg0).fetch(/window.bevyLoadingBarFetch(/' -e 's/input = fetch(/input = window.bevyLoadingBarFetch(/' examples/wasm/target/wasm_example.js").run().unwrap(); + } + let _ = fs::rename( Path::new("examples/wasm/target/wasm_example.js"), &example_path.join("wasm_example.js"), From 6c8ba4baa4a762f8e2c72d048c8bee27d9edac33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 6 May 2023 22:08:33 +0200 Subject: [PATCH 6/6] forgot to set the code path --- tools/example-showcase/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs index ca68073e5641d..78dd72832a126 100644 --- a/tools/example-showcase/src/main.rs +++ b/tools/example-showcase/src/main.rs @@ -222,7 +222,7 @@ technical_name = \"{}\" link = \"examples-webgpu/{}/{}\" image = \"../static/screenshots/{}/{}.png\" code_path = \"content/examples-webgpu/{}\" -github_code_path = \"examples/$category_path/$code_filename\" +github_code_path = \"{}\" header_message = \"Examples (WebGPU)\" +++", to_show.name, @@ -237,7 +237,8 @@ header_message = \"Examples (WebGPU)\" .components() .skip(1) .collect::() - .display() + .display(), + &to_show.path, ) .as_bytes(), )