Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "inspect" command #23

Merged
merged 9 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub enum Commands {
/// Show runtime stats for a running device (or emulator).
Monitor(MonitorArgs),

/// Inspect contents of the ROM: files, metadata, wasm binary.
Inspect(InspectArgs),

/// Run interactive session.
Repl(ReplArgs),

Expand Down Expand Up @@ -125,13 +128,9 @@ pub struct ExportArgs {
#[arg(long, default_value = ".")]
pub root: PathBuf,

/// Author ID.
#[arg(long, default_value = None)]
pub author: Option<String>,

/// App ID.
/// Full app ID.
#[arg(long, default_value = None)]
pub app: Option<String>,
pub id: Option<String>,

/// Path to the archive.
#[arg(short, long, default_value = None)]
Expand All @@ -158,6 +157,19 @@ pub struct ImportArgs {
#[derive(Debug, Parser)]
pub struct MonitorArgs {}

#[derive(Debug, Parser)]
pub struct InspectArgs {
/// ID of the ROM to inspect.
///
/// If not specified, the ID of the current project is used.
#[arg(default_value = None)]
pub id: Option<String>,

/// Path to the project root.
#[arg(long, default_value = ".")]
pub root: PathBuf,
}

#[derive(Debug, Parser)]
pub struct CheatArgs {
/// The command to pass into the app.
Expand Down
12 changes: 3 additions & 9 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
use crate::args::CatalogCommands;
use crate::args::{Commands, KeyCommands};
use crate::build::cmd_build;
use crate::catalog::{cmd_catalog_list, cmd_catalog_show};
use crate::cheat::cmd_cheat;
use crate::export::cmd_export;
use crate::import::cmd_import;
use crate::keys::{cmd_key_add, cmd_key_new, cmd_key_priv, cmd_key_pub, cmd_key_rm};
use crate::monitor::cmd_monitor;
use crate::repl::cmd_repl;
use crate::vfs::cmd_vfs;
#[allow(clippy::wildcard_imports)]
use crate::commands::*;
use std::fmt::Display;
use std::path::PathBuf;

Expand All @@ -19,6 +12,7 @@ pub fn run_command(vfs: PathBuf, command: &Commands) -> anyhow::Result<()> {
Commands::Import(args) => cmd_import(&vfs, args),
Commands::Cheat(args) => cmd_cheat(args),
Commands::Monitor(args) => cmd_monitor(&vfs, args),
Commands::Inspect(args) => cmd_inspect(&vfs, args),
Commands::Repl(args) => cmd_repl(&vfs, args),
Commands::Key(KeyCommands::New(args)) => cmd_key_new(&vfs, args),
Commands::Key(KeyCommands::Add(args)) => cmd_key_add(&vfs, args),
Expand Down
33 changes: 4 additions & 29 deletions src/build.rs → src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::args::BuildArgs;
use crate::config::{Config, FileConfig};
use crate::crypto::hash_dir;
use crate::file_names::{HASH, KEY, META, SIG};
use crate::fs::{collect_sizes, format_size};
use crate::images::convert_image;
use crate::langs::build_bin;
use crate::vfs::init_vfs;
Expand Down Expand Up @@ -245,32 +246,16 @@ fn write_sig(config: &Config) -> anyhow::Result<()> {
Ok(())
}

/// Get size in bytes for every file in the ROM directory.
fn collect_sizes(root: &Path) -> HashMap<OsString, u64> {
let mut sizes = HashMap::new();
let Ok(entries) = fs::read_dir(root) else {
return sizes;
};
for entry in entries {
let Ok(entry) = entry else {
continue;
};
let Ok(meta) = entry.metadata() else { continue };
sizes.insert(entry.file_name(), meta.len());
}
sizes
}

/// Check that there are now big or empty files in the ROM.
fn check_sizes(sizes: &HashMap<OsString, u64>) -> anyhow::Result<()> {
const MB: u64 = 1024 * 1024;
for (name, size) in sizes {
if *size == 0 {
let name = name.to_str().unwrap_or("___");
let name = name.to_str().unwrap_or("???");
bail!("the file {name} is empty");
}
if *size > 10 * MB {
let name = name.to_str().unwrap_or("___");
let name = name.to_str().unwrap_or("???");
bail!("the file {name} is too big");
}
}
Expand Down Expand Up @@ -303,17 +288,7 @@ fn print_sizes(old_sizes: &HashMap<OsString, u64>, new_sizes: &HashMap<OsString,
}
};

// convert big file size into Kb or Mb.
let new_size = if *new_size > 1024 * 1024 {
let new_size = new_size / 1024 / 1024;
format!("{new_size:>7} {}", "Mb".magenta())
} else if *new_size > 1024 {
let new_size = new_size / 1024;
format!("{new_size:>7} {}", "Kb".blue())
} else {
format!("{new_size:>10}")
};

let new_size = format_size(*new_size);
println!("{name:16} {new_size}{suffix}");
}
}
Expand Down
File renamed without changes.
File renamed without changes.
9 changes: 6 additions & 3 deletions src/export.rs → src/commands/export.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::args::ExportArgs;
use crate::config::Config;
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use std::fs::{read_dir, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
Expand All @@ -23,8 +23,11 @@ pub fn cmd_export(vfs: &Path, args: &ExportArgs) -> Result<()> {
}

fn get_id(vfs: PathBuf, args: &ExportArgs) -> Result<(String, String)> {
let res = if let (Some(author), Some(app)) = (&args.author, &args.app) {
(author.to_string(), app.to_string())
let res = if let Some(id) = &args.id {
let Some((author_id, app_id)) = id.split_once('.') else {
bail!("invalid app id: dot not found");
};
(author_id.to_string(), app_id.to_string())
} else {
let config = Config::load(vfs, &args.root).context("read project config")?;
(config.author_id, config.app_id)
Expand Down
File renamed without changes.
156 changes: 156 additions & 0 deletions src/commands/inspect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use crate::args::InspectArgs;
use crate::config::Config;
use crate::file_names::{BIN, META};
use crate::fs::{collect_sizes, format_size};
use anyhow::{bail, Context, Result};
use crossterm::style::Stylize;
use firefly_types::Meta;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use wasmparser::Parser;
use wasmparser::Payload::*;

pub fn cmd_inspect(vfs: &Path, args: &InspectArgs) -> Result<()> {
let (author_id, app_id) = get_id(vfs.to_path_buf(), args)?;
let rom_path = vfs.join("roms").join(&author_id).join(&app_id);
if !rom_path.exists() {
bail!("app {author_id}.{app_id} is not installed");
}

{
let sizes = collect_sizes(&rom_path);
print_sizes(&sizes);
}
{
let meta_path = rom_path.join(META);
let raw = fs::read(meta_path)?;
let meta = Meta::decode(&raw)?;
print_meta(&meta);
}
{
let bin_path = rom_path.join(BIN);
let wasm_stats = inspect_wasm(&bin_path)?;
print_wasm_stats(&wasm_stats);
}
Ok(())
}

fn get_id(vfs: PathBuf, args: &InspectArgs) -> Result<(String, String)> {
let res = if let Some(id) = &args.id {
let Some((author_id, app_id)) = id.split_once('.') else {
bail!("invalid app id: dot not found");
};
(author_id.to_string(), app_id.to_string())
} else {
let config = Config::load(vfs, &args.root).context("read project config")?;
(config.author_id, config.app_id)
};
Ok(res)
}

#[derive(Default)]
struct WasmStats {
imports: Vec<(String, String)>,
exports: Vec<String>,
memory: u64,
globals: u32,
functions: u32,
code_size: u32,
data_size: usize,
}

fn inspect_wasm(bin_path: &Path) -> anyhow::Result<WasmStats> {
let parser = Parser::new(0);
let mut stats = WasmStats::default();
let input_bytes = std::fs::read(bin_path).context("read wasm binary")?;
let input = parser.parse_all(&input_bytes);
for payload in input {
let payload = payload?;
match payload {
ImportSection(imports) => {
for import in imports {
let import = import?;
let name = (import.module.to_owned(), import.name.to_owned());
stats.imports.push(name);
}
}
GlobalSection(globals) => {
stats.globals = globals.count();
}
MemorySection(memories) => {
for memory in memories {
let memory = memory?;
stats.memory += memory.initial;
}
}
ExportSection(exports) => {
for export in exports {
let export = export?;
stats.exports.push(export.name.to_owned());
}
}
DataSection(datas) => {
for data in datas {
let data = data?;
stats.data_size += data.data.len();
}
}
CodeSectionStart { count, size, .. } => {
stats.code_size = size;
stats.functions = count;
}
_ => {}
}
}
stats.imports.sort();
stats.exports.sort();
Ok(stats)
}

fn print_meta(meta: &Meta) {
println!("{}", "metadata".blue());
println!(" {} {}", "author ID: ".cyan(), meta.author_id);
println!(" {} {}", "app ID: ".cyan(), meta.app_id);
println!(" {} {}", "author name: ".cyan(), meta.author_name);
println!(" {} {}", "app name: ".cyan(), meta.app_name);
println!(" {} {}", "launcher: ".cyan(), meta.launcher);
println!(" {} {}", "sudo: ".cyan(), meta.sudo);
println!(" {} {}", "version: ".cyan(), meta.version);
println!();
}

fn print_sizes(sizes: &HashMap<OsString, u64>) {
println!("{}", "files".blue());
let width = sizes.iter().map(|(n, _)| n.len()).max().unwrap_or_default();
for (name, size) in sizes {
let name = name.to_str().unwrap_or("???");
let size = format_size(*size);
println!(" {name:width$} {size}");
}
println!();
}

fn print_wasm_stats(stats: &WasmStats) {
let code_size = format_size(stats.code_size.into());
let code_size = code_size.trim_start();
let data_size = format_size(stats.data_size as u64);
let data_size = data_size.trim_start();

println!("{}", "wasm binary".blue());
println!(" {}: {}", "code size".cyan(), code_size);
println!(" {}: {}", "data size".cyan(), data_size);
println!(" {}: {}", "functions".cyan(), stats.functions);
println!(" {}: {}", "globals".cyan(), stats.globals);
println!(" {}: {} page(s)", "memory".cyan(), stats.memory);
println!(" {}: {}", "imports".cyan(), stats.imports.len());
for (mod_name, func_name) in &stats.imports {
let mod_name = mod_name.clone().magenta();
println!(" {mod_name}.{func_name}");
}
println!(" {}: {}", "exports".cyan(), stats.exports.len());
for export in &stats.exports {
println!(" {export}");
}
}
File renamed without changes.
21 changes: 21 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod build;
mod catalog;
mod cheat;
mod export;
mod import;
mod inspect;
mod keys;
mod monitor;
mod repl;
mod vfs;

pub use build::cmd_build;
pub use catalog::{cmd_catalog_list, cmd_catalog_show};
pub use cheat::cmd_cheat;
pub use export::cmd_export;
pub use import::cmd_import;
pub use inspect::cmd_inspect;
pub use keys::{cmd_key_add, cmd_key_new, cmd_key_priv, cmd_key_pub, cmd_key_rm};
pub use monitor::cmd_monitor;
pub use repl::cmd_repl;
pub use vfs::cmd_vfs;
File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions src/commands/vfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::vfs::get_vfs_path;

#[allow(clippy::unnecessary_wraps)]
pub fn cmd_vfs() -> anyhow::Result<()> {
let path = get_vfs_path();
let path = path.to_str().unwrap();
println!("{path}");
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_smoke_cmd_vfs() {
cmd_vfs().unwrap();
}
}
34 changes: 34 additions & 0 deletions src/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crossterm::style::Stylize;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::path::Path;

/// Get size in bytes for every file in the ROM directory.
pub fn collect_sizes(root: &Path) -> HashMap<OsString, u64> {
let mut sizes = HashMap::new();
let Ok(entries) = fs::read_dir(root) else {
return sizes;
};
for entry in entries {
let Ok(entry) = entry else {
continue;
};
let Ok(meta) = entry.metadata() else { continue };
sizes.insert(entry.file_name(), meta.len());
}
sizes
}

/// Convert big file size into Kb or Mb.
pub fn format_size(size: u64) -> String {
if size > 1024 * 1024 {
let new_size = size / 1024 / 1024;
format!("{new_size:>5} {}", "Mb".magenta())
} else if size > 1024 {
let new_size = size / 1024;
format!("{new_size:>5} {}", "Kb".blue())
} else {
format!("{size:>8}")
}
}
Loading
Loading