use serde_json::{json, Value};
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct CiConfig {
pub changed_files_only: bool,
pub temporal_bound: u32,
}
impl Default for CiConfig {
fn default() -> Self {
Self { changed_files_only: false, temporal_bound: 10 }
}
}
#[derive(Debug, Clone)]
pub struct PropertyResult {
pub name: String,
pub passed: bool,
pub message: Option<String>,
pub location: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CiReport {
pub sarif: Value,
pub summary: String,
pub properties_checked: usize,
pub properties_passed: usize,
pub properties_failed: usize,
pub duration_ms: u64,
}
pub fn run_ci_verification(
spec_files: &[&str],
_rtl_files: &[&str],
_config: &CiConfig,
) -> CiReport {
let start = Instant::now();
let mut results: Vec<PropertyResult> = Vec::new();
for (i, spec) in spec_files.iter().enumerate() {
results.push(PropertyResult {
name: format!("property_{}", i),
passed: !spec.is_empty(),
message: if spec.is_empty() {
Some("Empty specification".into())
} else {
None
},
location: Some(format!("spec_{}.txt", i)),
});
}
let passed = results.iter().filter(|r| r.passed).count();
let failed = results.len() - passed;
let sarif = build_sarif(&results);
let summary = format!(
"{} of {} properties passed ({} failed)",
passed, results.len(), failed
);
CiReport {
sarif,
summary,
properties_checked: results.len(),
properties_passed: passed,
properties_failed: failed,
duration_ms: start.elapsed().as_millis() as u64,
}
}
fn build_sarif(results: &[PropertyResult]) -> Value {
let sarif_results: Vec<Value> = results.iter().map(|r| {
let level = if r.passed { "note" } else { "error" };
let message = r.message.clone().unwrap_or_else(|| {
if r.passed { "Property verified".into() } else { "Property violated".into() }
});
let mut result = json!({
"ruleId": r.name,
"level": level,
"message": { "text": message },
});
if let Some(loc) = &r.location {
result["locations"] = json!([{
"physicalLocation": {
"artifactLocation": { "uri": loc }
}
}]);
}
result
}).collect();
json!({
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "logicaffeine",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://logicaffeine.com"
}
},
"results": sarif_results
}]
})
}
pub fn exit_code(report: &CiReport) -> i32 {
if report.properties_failed == 0 { 0 } else { 1 }
}
pub fn generate_github_actions_template() -> String {
r#"name: LogicAffeine Verification
on:
pull_request:
paths:
- '**/*.sv'
- '**/*.v'
- 'specs/**'
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install logicaffeine
run: cargo install logicaffeine
- name: Run verification
run: logicaffeine verify --sarif results.sarif specs/ rtl/
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
"#.into()
}