@@ -2,14 +2,22 @@
//! crafted TOML files. The format of these files follows the following
//! rules.
+mod spec;
+
+use std::path::PathBuf;
+
use clap::Parser;
use env_logger::Env;
-use log::info;
+use log::{error, info};
/// A CLI tool to generate struct definitions in several languages.
#[derive(Parser, Debug)]
#[command(version, about)]
-struct Cli;
+struct Cli {
+ /// Path to the input directory containing the hypercall specification
+ #[arg(short, long)]
+ indir: PathBuf,
+}
fn main() {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
@@ -17,5 +25,19 @@ fn main() {
let cli = Cli::parse();
info!("args: {:?}", cli);
- todo!("read spec files and generate output files");
+ let _specification = match spec::Spec::new(&cli.indir) {
+ Ok(x) => x,
+ Err(spec::Error::Toml(x)) => {
+ error!("TOML parsing error:");
+ error!("{x:#?}");
+ std::process::exit(1);
+ }
+ Err(spec::Error::Io(x)) => {
+ error!("IO error:");
+ error!("{x:#?}");
+ std::process::exit(1);
+ }
+ };
+
+ todo!("generate output files");
}
new file mode 100644
@@ -0,0 +1,96 @@
+//! Specification descriptions
+//!
+//! The TOML files are not parsed by hand. This module provides a sort of
+//! schema for the TOML descriptions, in the sense that `serde` itself ensures
+//! every field is deserialised into its equivalent Rust structure or the
+//! deserialization procedure fails.
+//!
+//! If the specification is clearly invalid (i.e: missing fields) it'll scream
+//! in a rather obvious way.
+//!
+//! A special case is the `typ` field in the specifications is meant to have
+//! the format present in the specifications under `extra`. This allows `serde`
+//! to properly decode the type and match it with a variant of the [`Typ`] type
+//! with the payload landing in the payload of the variant itself.
+
+use std::{fs::read_to_string, path::Path};
+
+use log::{debug, info};
+
+/// A language-agnostic specification.
+#[derive(Debug, serde::Deserialize)]
+struct InFileDef;
+
+/// Description of an abstract output (i.e: `.rs`, `.h`, etc).
+///
+/// Contains every element of the ABI that needs representing.
+#[derive(Debug)]
+pub struct OutFileDef {
+ /// The name of the output file, without the final extension.
+ pub name: String,
+}
+
+impl OutFileDef {
+ /// Creates a new _output_ file description. Each [`OutFileDef`] is
+ /// associated with a number of [`InFileDef`] and holds the merged
+ /// contents described in all of them.
+ ///
+ /// # Errors
+ /// Fails if the TOML is invalid or on IO error.
+ pub fn new(name: String, dir: &Path) -> Result<Self, Error> {
+ info!("Reading {dir:?} to generate an output file");
+
+ let mut ret = Self { name };
+
+ for entry in from_ioerr(dir.read_dir())? {
+ let path = from_ioerr(entry)?.path();
+ debug!("Reading {:?} to generate outfile={}", path, ret.name);
+ let toml_str = from_ioerr(read_to_string(path))?;
+ let filedef: InFileDef = toml::from_str(&toml_str).map_err(Error::Toml)?;
+ }
+
+ Ok(ret)
+ }
+}
+
+/// Internal error type for every error spec parsing could encounter
+#[derive(Debug)]
+pub enum Error {
+ /// Wrapper around IO errors
+ Io(std::io::Error),
+ /// Wrapper around deserialization errors
+ Toml(toml::de::Error),
+}
+
+/// Maps an [`std::io::Error`] onto a [`Error`] type for easier propagation
+fn from_ioerr<T>(t: std::io::Result<T>) -> Result<T, Error> {
+ t.map_err(Error::Io)
+}
+
+/// Object containing the abstract definitions of all output files.
+///
+/// See [`OutFileDef`] to details on the specification contents of each output.
+#[derive(Debug)]
+pub struct Spec(pub Vec<OutFileDef>);
+
+impl Spec {
+ /// Creates a new abstract specification from a top-level directory full
+ /// of specification files. This is used later to aggregate all the content
+ /// and generate the appropriate language outputs.
+ ///
+ /// # Errors
+ /// Fails on IO errors.
+ pub fn new(root: &Path) -> Result<Self, Error> {
+ info!("Reading {root:?} as top-level directory");
+
+ let mut ret = Self(Vec::new());
+ for outfile in from_ioerr(root.read_dir())? {
+ // Each folder in the root defines a single output file
+ let outfile = from_ioerr(outfile)?;
+ let name = outfile.file_name().to_string_lossy().to_string();
+ ret.0.push(OutFileDef::new(name, &outfile.path())?);
+ }
+
+ Ok(ret)
+ }
+}
There isn't any deserialisation yet. It's mere plumbing. Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com> --- tools/rust/xenbindgen/src/main.rs | 28 ++++++++- tools/rust/xenbindgen/src/spec.rs | 96 +++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 tools/rust/xenbindgen/src/spec.rs