From a4752dbb5986cba93c2bd662a8bd5d1c82eeefb3 Mon Sep 17 00:00:00 2001 From: bunnie Date: Wed, 2 Feb 2022 16:18:06 +0800 Subject: [PATCH] app manifest and auto-gen infrastructure --- Cargo.lock | 2 + locales/build.rs | 2 +- services/status/locales/i18n.json | 12 -- xtask/Cargo.toml | 2 + xtask/src/main.rs | 211 ++++++++++++++++++++++++++---- 5 files changed, 189 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7483f098b..4dda59662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2916,6 +2916,8 @@ dependencies = [ "curl", "filetime", "rustc_version 0.4.0", + "serde", + "serde_json", "zip", ] diff --git a/locales/build.rs b/locales/build.rs index b1e4ec209..d32dccccf 100644 --- a/locales/build.rs +++ b/locales/build.rs @@ -23,7 +23,7 @@ fn read_locales() -> Translations { let project_dir = project_root(); let build_directory = project_dir.to_str().unwrap(); - let locales = format!("{}/**/locales/**/i18n.json", build_directory); + let locales = format!("{}/**/i18n.json", build_directory); println!("Reading {}", &locales); for entry in glob(&locales).expect("Failed to read glob pattern") { diff --git a/services/status/locales/i18n.json b/services/status/locales/i18n.json index 98852b165..6c31a0039 100644 --- a/services/status/locales/i18n.json +++ b/services/status/locales/i18n.json @@ -126,18 +126,6 @@ "zh": "充电时睡不着", "en-audio": "Can't sleep while charging" }, - "appmenu.ball": { - "en": "Simple ball demo", - "ja": "シンプルなボールのデモ", - "zh": "简单的球演示", - "en-audio": "Simple ball demo" - }, - "appmenu.repl": { - "en": "Basic REPL demo", - "ja": "シンプルなREPLデモ", - "zh": "基本 REPL 演示", - "en-audio": "Basic REPL demo" - }, "appmenu.shellchat": { "en": "Shellchat", "ja": "Shellchat", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 1b61edb03..5955ae3d7 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -13,3 +13,5 @@ filetime = "0.2.14" rustc_version = "0.4.0" zip = "0.5.13" chrono = "0.4" +serde_json = "1.0.41" +serde = {version = "1.0.130", features = ["derive"]} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 4cf0bd4ff..64999854d 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,6 +7,7 @@ use std::{ }; use std::fs::OpenOptions; use chrono::Local; +use std::collections::{HashMap, BTreeMap}; type DynError = Box; @@ -64,7 +65,9 @@ fn main() -> Result<(), Box> { "dns", "pddb", "modals", - // apps + ]; + let app_pkgs = [ + // "standard" demo apps "ball", "repl", ]; @@ -176,12 +179,37 @@ fn main() -> Result<(), Box> { } Some("renode-aes-test") => renode_image(false, &aestest_pkgs, &[])?, Some("renode-image-debug") => renode_image(true, &hw_pkgs, &[])?, - Some("run") => run(false, &hw_pkgs, None)?, Some("pddb-ci") => run(false, &hw_pkgs, Some( &["--features", "pddb/ci", "--features", "pddb/deterministic"] ))?, + Some("app-image") => { + let mut args = env::args(); + args.nth(1); + let mut pkgs = base_pkgs.to_vec(); + let apps: Vec = args.collect(); + for app in &apps { + pkgs.push(app); + } + generate_app_menus(&apps); + build_hw_image(false, + Some("./precursors/soc.svd".to_string()), + &pkgs, + lkey, kkey, None, &[])? + } Some("hw-image") => { - build_hw_image(false, env::args().nth(2), &hw_pkgs, lkey, kkey, None, &[])? + let mut pkgs = vec![]; + for pkg in hw_pkgs { + pkgs.push(pkg); + } + for app in app_pkgs { + pkgs.push(app); + } + let mut app_strs = Vec::::new(); + for app in app_pkgs { + app_strs.push(app.to_string()); + } + generate_app_menus(&app_strs); + build_hw_image(false, env::args().nth(2), &pkgs, lkey, kkey, None, &[])? } Some("gfx-dev") => { run(true, &gfx_dev_pkgs, Some(&["--features", "graphics-server/testing"]))? @@ -268,31 +296,45 @@ fn main() -> Result<(), Box> { fn print_help() { eprintln!( "Tasks: -renode-image builds a functional image for renode -renode-test builds a test image for renode -renode-image-debug builds a test image for renode in debug mode -libstd-test [pkg1] [..] builds a test image that includes the minimum packages, plus those - specified on the command line (e.g. built externally) -hw-image [soc.svd] [loader.key] [kernel.key] builds an image for real hardware -run runs a release build using a hosted environment -debug runs a debug build using a hosted environment -benchmark [soc.svd] builds a benchmarking image for real hardware -minimal [soc.svd] builds a minimal image for API testing -cbtest builds an image for callback testing -trng-test [soc.svd] builds an image for TRNG testing - urandom source seeded by TRNG+AV -ro-test [soc.svd] builds an image for ring oscillator only TRNG testing -av-test [soc.svd] builds an image for avalanche generater only TRNG testing -sr-test [soc.svd] builds the suspend/resume testing image -burn-kernel invoke the `usb_update.py` utility to burn the kernel -burn-loader invoke the `usb_update.py` utility to burn the loader -burn-soc invoke the `usb_update.py` utility to stage the SoC gateware, which must then be provisioned with secret material using the Precursor device. -nuke-soc 'Factory reset' - invoke the `usb_update.py` utility to burn the SoC gateware, erasing most secrets. For developers. -generate-locales (re)generate the locales include for the language selected in xous-rs/src/locale.rs -wycheproof-import generate binary test vectors for engine-25519 from whycheproof-import/x25519.json -pddb-dev PDDB testing only for live hardware -pddb-hosted PDDB testing in a hosted environment +Hardware images: + hw-image [soc.svd] builds an image for real hardware with baseline demo apps + [loader.key] plus signing key options + [kernel.key] + app-image [app1] [..] builds an image for real hardware of baseline kernel + specified apps + +Hosted emulation: + run [app1] [..] runs a release build using a hosted environment plus specified apps + +Renode emulation: + renode-image builds a functional image for renode + renode-test builds a test image for renode + renode-image-debug builds a test image for renode in debug mode + libstd-test [pkg1] [..] builds a test image that includes the minimum packages, plus those + specified on the command line (e.g. built externally) + +Locale (re-)generation: + generate-locales (re)generate the locales include for the language selected in xous-rs/src/locale.rs + +Direct USB updates: + ** Please refer to tools/README_UPDATE.md for instructions on how to set up `usb_update.py` ** + burn-kernel invoke the `usb_update.py` utility to burn the kernel + burn-loader invoke the `usb_update.py` utility to burn the loader + burn-soc invoke the `usb_update.py` utility to stage the SoC gateware, which must then be provisioned with secret material using the Precursor device. + nuke-soc 'Factory reset' - invoke the `usb_update.py` utility to burn the SoC gateware, erasing most secrets. For developers. + +Various debug configurations: + debug runs a debug build using a hosted environment + benchmark [soc.svd] builds a benchmarking image for real hardware + minimal [soc.svd] builds a minimal image for API testing + cbtest builds an image for callback testing + trng-test [soc.svd] builds an image for TRNG testing - urandom source seeded by TRNG+AV + ro-test [soc.svd] builds an image for ring oscillator only TRNG testing + av-test [soc.svd] builds an image for avalanche generater only TRNG testing + sr-test [soc.svd] builds the suspend/resume testing image + wycheproof-import generate binary test vectors for engine-25519 from whycheproof-import/x25519.json + pddb-dev PDDB testing only for live hardware + pddb-hosted PDDB testing in a hosted environment -Please refer to tools/README_UPDATE.md for instructions on how to set up `usb_update.py` " ) } @@ -1068,4 +1110,119 @@ pub(crate) fn get_version() -> crate::api::VersionString { } "####; out.write_all(s.as_bytes()).expect("couldn't write our version template header"); +} + +use serde::{Deserialize, Serialize}; +use std::string::String; +#[derive(Deserialize, Serialize, Debug)] +struct AppManifest { + context_name: String, + menu_name: HashMap>, +} +#[derive(Deserialize, Serialize, Debug)] +struct Locales { + locales: HashMap>, +} + +fn generate_app_menus(apps: &Vec::) { + let file = File::open("apps/manifest.json").expect("Failed to open the manifest file"); + let mut reader = std::io::BufReader::new(file); + let mut content = String::new(); + reader + .read_to_string(&mut content) + .expect("Failed to read the file"); + let manifest: HashMap = + serde_json::from_str(&content).expect("Cannot parse manifest file"); + + let mut working_set = BTreeMap::::new(); + let mut l = HashMap::>::new(); + + for app in apps { + if let Some(manifest) = manifest.get(app) { + working_set.insert(app.to_string(), &manifest); + for (name, translations) in &manifest.menu_name { + let mut map = HashMap::::new(); + for (language, phrase) in translations { + map.insert(language.to_string(), phrase.to_string()); + } + l.insert(name.to_string(), map); + } + } else { + println!("Warning: app '{}' not found in `apps/manifest.json`, ignoring.", app); + } + } + + // output a JSON localizations file + let mut locale_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("apps/i18n.json").expect("Can't open our version file for writing"); + write!(locale_file, "{}", serde_json::to_string(&l).unwrap()).expect("couldn't write to the app locale file"); + + // output the Rust manifests + let mut gam_tokens = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("services/gam/src/apps.rs").expect("Can't open our gam manifest for writing"); + writeln!(gam_tokens, "// This file is auto-generated by xtask/main.rs generate_app_menus()").unwrap(); + for (app_name, manifest) in working_set.iter() { + writeln!(gam_tokens, "pub const APP_NAME_{}: &'static str = \"{}\";", + app_name.to_uppercase(), + manifest.context_name, + ).unwrap(); + } + writeln!(gam_tokens, "\npub const EXPECTED_APP_CONTEXTS: &[&'static str] = &[").unwrap(); + for (app_name, _manifest) in working_set.iter() { + writeln!(gam_tokens, " APP_NAME_{},", + app_name.to_uppercase(), + ).unwrap(); + } + writeln!(gam_tokens, "];").unwrap(); + + let mut menu = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("services/status/src/app_autogen.rs").expect("Can't open our gam manifest for writing"); + writeln!(menu, "// This file is auto-generated by xtask/main.rs generate_app_menus()").unwrap(); + + writeln!(menu, r####"use crate::StatusOpcode; + use gam::{{MenuItem, MenuPayload}}; + use locales::t; + use num_traits::*; + + pub(crate) fn app_dispatch(gam: &gam::Gam, token: [u32; 4], index: usize) {{ + match index {{ + "####).unwrap(); + for (index, (app_name, _manifest)) in working_set.iter().enumerate() { + writeln!(menu, "{} => gam.switch_to_app(gam::APP_NAME_{}, token).expect(\"couldn't raise app\"),", + index, + app_name.to_uppercase() + ).unwrap(); + } + writeln!(menu, r####"_ => log::error!("Invalid index for app dispatch: {{}}. Ignoring!", index), + }} +}} + +pub(crate) fn app_menu_items(menu_items: &mut Vec::, status_conn: u32) {{ +"####).unwrap(); + for (index, (_app_name, manifest)) in working_set.iter().enumerate() { + writeln!(menu, " menu_items.push(MenuItem {{",).unwrap(); + assert!(manifest.menu_name.len() == 1, "Improper menu name record entry"); + for name in manifest.menu_name.keys() { + writeln!(menu, " name: xous_ipc::String::from_str(t!(\"{}\", xous::LANG)),", + name).unwrap(); + } + writeln!(menu, " action_conn: Some(status_conn),",).unwrap(); + writeln!(menu, " action_opcode: StatusOpcode::SwitchToApp.to_u32().unwrap(),",).unwrap(); + writeln!(menu, " action_payload: MenuPayload::Scalar([{}, 0, 0, 0]),", index).unwrap(); + writeln!(menu, " close_on_select: true,",).unwrap(); + writeln!(menu, " }});\n",).unwrap(); + } + writeln!(menu, "}}").unwrap(); } \ No newline at end of file