Xml metadata embedding

This commit is contained in:
Wataru Otsubo 2025-02-12 13:10:48 +00:00
parent f714b42249
commit 9555b743f9
11 changed files with 161 additions and 62 deletions

View file

@ -21,7 +21,7 @@ stages: # List of stages for jobs, and their order of execution
- test
.setup-rust:
image: rust:latest
image: registry.cern.ch/docker.io/library/rust:latest
before_script:
- rustup component add clippy rustfmt

View file

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Embedding XML git metadata
- New high-level API (`generate` function)
### Fixed
- Loosen Cargo.toml dependencies

View file

@ -25,8 +25,9 @@ rustupのインストールには公式サイトにある`curl`スクリプト
### バイナリクレートの使い方
```bash
cargo build --bins --release
cargo build --features=bin --bins --release
```
を実行します。
`--release`はオプションです。

View file

@ -28,7 +28,7 @@ Note that `cargo` is available on lxplus, so you might be able to use that (it i
Execute
```bash
cargo build --bins --release
cargo build --features=bin --bins --release
```
to build.

View file

@ -13,7 +13,7 @@ use thiserror::Error;
use crate::parser::{ParseCustomBool, ParseEnumError, ParsePrefixedU32, ParsePrefixedU32Error};
use crate::types::{
BitString, Block, Field, Fifo, Memory, Module, ModuleBlockElements, MultipleParams, Register,
Value,
Value, XmlGitInfo,
};
/// Possible errors in conversion, with positional information.
@ -164,7 +164,7 @@ mod util {
}
impl Module {
pub fn from_xml_dom(node: Node) -> Result<Self, DomConversionError> {
pub fn from_xml_dom(node: Node, git_info: XmlGitInfo) -> Result<Self, DomConversionError> {
assert!(node.parent().unwrap().is_root());
assert_eq!(node.tag_name().name(), "module");
@ -212,6 +212,7 @@ impl Module {
elements_bitstring: child_bitstrings,
elements_other: child_other,
xmlhash: Sha256::digest(node.document().input_text()).into(),
git_info,
})
}
}

View file

@ -130,6 +130,8 @@ impl Module {
pub fn generate_code(self) -> Result<HashMap<path::PathBuf, syn::File>, CodeGenError> {
let build_metadata = format!(
"
This code is auto generated using endcap_sl_software_ri_generator.
# Build metadata
- timestamp: {}
@ -137,9 +139,9 @@ impl Module {
## CSR XML
- sha256: {}
- git describe: TODO (after building step is fixed)
- git commit timestamp: TODO
- git SHA: TODO
- git describe: {}
- git commit timestamp: {}
- git SHA: {}
## Generator
@ -150,6 +152,9 @@ impl Module {
",
Local::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, false),
hex::encode(self.xmlhash),
self.git_info.describe,
self.git_info.commit_timestamp,
self.git_info.sha,
GENERATOR_BUILD_TIMESTAMP,
GENERATOR_GIT_DESCRIBE,
GENERATOR_GIT_COMMIT_TIMESTAMP,
@ -364,7 +369,6 @@ impl CodeGen for Block {
}
};
let (out_path, next_parent_path) = mod_file_path(parent_path, &snake_case_name);
log::info!("{:?}", out_path);
if let Some(old_out) = files.insert(out_path.clone(), out.clone()) {
log::error!("path {}", out_path.display());
log::error!("old {}", old_out.to_string());

58
src/integrated.rs Normal file
View file

@ -0,0 +1,58 @@
use std::path;
use thiserror::Error;
use crate::{
converter, generator,
io::{get_xml_gitinfo, XmlGitInfoError},
types,
};
#[derive(Debug, Error)]
pub enum Error {
#[error("error in getting xml git info: {0}")]
XmlGitInfoError(#[from] XmlGitInfoError),
#[error("io error: {0}")]
IOError(#[from] std::io::Error),
#[error("xml parse error: {0}")]
XmlParseError(#[from] roxmltree::Error),
#[error("dom conversion error: {0}")]
DomConversionError(#[from] converter::DomConversionError),
#[error("code generation error: {0}")]
CodeGenError(#[from] generator::CodeGenError),
}
/// Generate register interface code from xml in `xml` and write them under directory `out`.
pub fn generate(xml: &path::Path, out: &path::Path) -> Result<(), Error> {
log::debug!("generate called: xml:{:?}, out: {:?}", xml, out);
let xml_git_info = get_xml_gitinfo(xml)?;
log::debug!("xml git info {xml_git_info:?}");
let xmlfile = std::fs::read_to_string(xml)?;
let doc = roxmltree::Document::parse_with_options(
&xmlfile,
roxmltree::ParsingOptions {
allow_dtd: true,
nodes_limit: u32::MAX,
},
)?;
log::debug!("xml parsed {doc:?}");
let register_map = types::Module::from_xml_dom(doc.root_element(), xml_git_info)?;
log::debug!("converted {register_map:?}");
let files = register_map.generate_code()?;
if log::log_enabled!(log::Level::Debug) {
for (path, code) in &files {
log::debug!("path: {:?}", path);
log::debug!("{}", prettyplease::unparse(code));
log::trace!("{}", prettyplease::unparse(code));
}
}
crate::write_to_files(files, out)?;
log::debug!("wrote to files");
Ok(())
}

View file

@ -1,6 +1,75 @@
//! File IO for generated codes.
use std::{collections::HashMap, fs, io, path};
use std::{collections::HashMap, fs, io, path, process};
use thiserror::Error;
use crate::types::XmlGitInfo;
#[derive(Debug, Error)]
pub enum XmlGitInfoError {
#[error("io error while getting xml git info: {msg} {source}")]
IOError {
msg: &'static str,
#[source]
source: io::Error,
},
#[error("git failed: {msg}: {stderr}")]
CommandFailed { msg: &'static str, stderr: String },
}
impl XmlGitInfoError {
fn io_error(e: io::Error, msg: &'static str) -> Self {
XmlGitInfoError::IOError { msg, source: e }
}
}
pub(crate) fn get_xml_gitinfo(xml_path: &path::Path) -> Result<XmlGitInfo, XmlGitInfoError> {
let describe = process::Command::new("git")
.args(["describe", "--always", "--dirty"])
.current_dir(xml_path.parent().unwrap())
.output()
.map_err(|e| XmlGitInfoError::io_error(e, "git describe"))?;
let describe = if describe.status.success() {
String::from_utf8_lossy(&describe.stdout).trim().to_owned()
} else {
return Err(XmlGitInfoError::CommandFailed {
msg: "git describe",
stderr: String::from_utf8_lossy(&describe.stderr).into_owned(),
});
};
let timestamp = process::Command::new("git")
.args(["log", "-1", "--pretty=format:'%cI'"])
.current_dir(xml_path.parent().unwrap())
.output()
.map_err(|e| XmlGitInfoError::io_error(e, "git log (timestamp)"))?;
let timestamp = if timestamp.status.success() {
String::from_utf8_lossy(&timestamp.stdout).trim().to_owned()
} else {
return Err(XmlGitInfoError::CommandFailed {
msg: "git log (timestamp)",
stderr: String::from_utf8_lossy(&timestamp.stderr).into_owned(),
});
};
let sha = process::Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(xml_path.parent().unwrap())
.output()
.map_err(|e| XmlGitInfoError::io_error(e, "git rev-parse (sha)"))?;
let git_sha = if sha.status.success() {
String::from_utf8_lossy(&sha.stdout).trim().to_owned()
} else {
return Err(XmlGitInfoError::CommandFailed {
msg: "git rev-parse (sha)",
stderr: String::from_utf8_lossy(&sha.stderr).into_owned(),
});
};
Ok(XmlGitInfo {
describe,
commit_timestamp: timestamp,
sha: git_sha,
})
}
/// Write formatted codes generated with [`Module::generate_code`](crate::types::Module::generate_code).
pub fn write_to_files(

View file

@ -8,26 +8,7 @@
//!
//! # Example
//!
//! Here's a typical usage:
//! ```no_run
//! use endcap_sl_software_ri_generator::types;
//!
//! let xmlfile = std::fs::read_to_string("./csr.xml").expect("failed to open file");
//! let doc = roxmltree::Document::parse_with_options(
//! &xmlfile,
//! roxmltree::ParsingOptions {
//! allow_dtd: true,
//! nodes_limit: u32::MAX,
//! },
//! )
//! .expect("failed to parse");
//!
//! let register_map = types::Module::from_xml_dom(doc.root_element()).expect("failed to convert");
//!
//! let files = register_map.generate_code().expect("failed to generate code");
//! endcap_sl_software_ri_generator::write_to_files(files,
//! &std::path::PathBuf::from("out")).expect("failed to write");
//! ```
//! See [`generate`].
//!
//! # Overview
//!
@ -44,10 +25,12 @@
pub mod converter;
pub mod generator;
pub mod integrated;
pub mod io;
pub mod meta;
mod parser;
mod type_traits;
pub mod types;
pub use integrated::generate;
pub use io::write_to_files;

View file

@ -1,9 +1,7 @@
use std::{fs, path};
use std::path;
use anyhow::Result;
use clap::Parser;
use endcap_sl_software_ri_generator::types;
use itertools::Itertools;
/// Generate register interface from register map xml.
#[derive(Parser, Debug)]
@ -21,35 +19,7 @@ fn main() -> Result<()> {
let args = Args::parse();
log::debug!("args: {:?}", args);
let xmlfile = fs::read_to_string(args.xml)?;
let doc = roxmltree::Document::parse_with_options(
&xmlfile,
roxmltree::ParsingOptions {
allow_dtd: true,
nodes_limit: u32::MAX,
},
)?;
log::debug!("Parsed: {:#?}", doc);
let register_map = types::Module::from_xml_dom(doc.root_element())?;
log::info!("read: {:?}", register_map);
log::debug!("read: {:#?}", register_map);
let files = register_map.generate_code()?;
if log::log_enabled!(log::Level::Debug) {
for (path, code) in &files {
log::debug!("path: {:?}", path);
log::debug!("{}", prettyplease::unparse(code));
log::trace!("{}", prettyplease::unparse(code));
}
}
if log::log_enabled!(log::Level::Info) {
for filepath in files.keys().sorted() {
log::info!("{}", filepath.display());
}
}
endcap_sl_software_ri_generator::write_to_files(files, &args.out)?;
endcap_sl_software_ri_generator::generate(&args.xml, &args.out)?;
Ok(())
}

View file

@ -11,6 +11,7 @@ pub struct Module {
pub elements_bitstring: Vec<BitString>,
pub elements_other: Vec<ModuleBlockElements>,
pub xmlhash: [u8; 32],
pub git_info: XmlGitInfo,
}
#[derive(Debug)]
@ -151,3 +152,10 @@ pub enum FieldFifoInterface {
Block,
Both,
}
#[derive(Debug)]
pub struct XmlGitInfo {
pub(crate) describe: String,
pub(crate) commit_timestamp: String,
pub(crate) sha: String,
}