mirror of
https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator.git
synced 2025-02-22 16:47:08 +09:00
Xml metadata embedding
This commit is contained in:
parent
f714b42249
commit
9555b743f9
11 changed files with 161 additions and 62 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,8 +25,9 @@ rustupのインストールには公式サイトにある`curl`スクリプト
|
|||
### バイナリクレートの使い方
|
||||
|
||||
```bash
|
||||
cargo build --bins --release
|
||||
cargo build --features=bin --bins --release
|
||||
```
|
||||
|
||||
を実行します。
|
||||
`--release`はオプションです。
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
58
src/integrated.rs
Normal 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(())
|
||||
}
|
71
src/io.rs
71
src/io.rs
|
@ -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(×tamp.stdout).trim().to_owned()
|
||||
} else {
|
||||
return Err(XmlGitInfoError::CommandFailed {
|
||||
msg: "git log (timestamp)",
|
||||
stderr: String::from_utf8_lossy(×tamp.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(
|
||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -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;
|
||||
|
|
34
src/main.rs
34
src/main.rs
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue