use std::fs;
use std::io::Write;
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let out_dir = std::env::var("OUT_DIR")?;
let generated_dir = Path::new(&out_dir).join("generated");
fs::create_dir_all(&generated_dir)?;
generate_temperature_struct(&generated_dir)?;
generate_mod_rs(&generated_dir)?;
let typed_test_dir =
Path::new(&std::env::var("CARGO_MANIFEST_DIR")?).join("../../target/typed-test");
let typed_test_file = typed_test_dir.join("interop_types.rs");
if !typed_test_file.exists() {
fs::create_dir_all(&typed_test_dir)?;
fs::write(&typed_test_file, generate_interop_stub())?;
}
println!("cargo:rerun-if-changed=../../target/typed-test/interop_types.rs");
println!("cargo:rerun-if-changed=build.rs");
Ok(())
}
const fn generate_interop_stub() -> &'static str {
"\
// Auto-generated stub -- real types created by scripts/test-sdk-typed.sh
// This stub allows cargo check/clippy --all-targets to pass.
// Do NOT edit -- it will be overwritten by hdds_gen.
use hdds::core::ser::{Cdr2Encode, Cdr2Decode, CdrError};
#[derive(Debug, Clone, PartialEq)]
#[repr(u32)]
pub enum SensorKind { TEMPERATURE = 0, PRESSURE = 1, HUMIDITY = 2 }
#[derive(Debug, Clone, PartialEq)]
pub struct GeoPoint { pub latitude: f64, pub longitude: f64 }
#[derive(Debug, Clone, PartialEq)]
pub struct SensorReading {
pub sensor_id: u32,
pub kind: SensorKind,
pub value: f32,
pub label: String,
pub timestamp_ns: i64,
pub history: Vec<f32>,
pub error_code: Option<i32>,
pub location: GeoPoint,
}
impl Cdr2Encode for SensorReading {
fn encode_cdr2_le(&self, _buf: &mut [u8]) -> Result<usize, CdrError> {
panic!(\"stub: run scripts/test-sdk-typed.sh to generate real interop types\")
}
fn max_cdr2_size(&self) -> usize { 0 }
}
impl Cdr2Decode for SensorReading {
fn decode_cdr2_le(_buf: &[u8]) -> Result<(Self, usize), CdrError> {
panic!(\"stub: run scripts/test-sdk-typed.sh to generate real interop types\")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct KeyedSample {
pub id: u32,
pub active: bool,
pub kind: SensorKind,
pub name: String,
pub origin: GeoPoint,
pub reading: f32,
}
impl Cdr2Encode for KeyedSample {
fn encode_cdr2_le(&self, _buf: &mut [u8]) -> Result<usize, CdrError> {
panic!(\"stub: run scripts/test-sdk-typed.sh to generate real interop types\")
}
fn max_cdr2_size(&self) -> usize { 0 }
}
impl Cdr2Decode for KeyedSample {
fn decode_cdr2_le(_buf: &[u8]) -> Result<(Self, usize), CdrError> {
panic!(\"stub: run scripts/test-sdk-typed.sh to generate real interop types\")
}
}
"
}
#[allow(clippy::too_many_lines, clippy::uninlined_format_args)]
fn generate_temperature_struct(output_dir: &Path) -> std::io::Result<()> {
let output_path = output_dir.join("temperature.rs");
let type_name = "Temperature";
let type_id = compute_fnv1a_hash(type_name);
let code = format!("// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright (c) 2025-2026 naskel.com
//
// Generated by build.rs (Phase 7a MVP + Task 1.2)
// DO NOT EDIT - Changes will be overwritten on next build
use hdds::core::ser::{{Cdr2Encode, Cdr2Decode, cursor::{{Cursor, CursorMut}}}};
/// Temperature sensor data (Phase 7a test case)
///
/// # Fields
///
/// - `value`: Temperature in degrees Celsius (f32)
/// - `timestamp`: Unix epoch seconds (i32, matches RTI IDL 'long')
///
/// # CDR2 Encoding
///
/// Wire format (8 bytes total, little-endian):
/// ```text
/// [f32 value][u32 timestamp]
/// ```
///
/// Both fields are 4-byte aligned, no padding required.
#[derive(Debug, Clone, PartialEq)]
pub struct Temperature {{
/// Temperature value in degrees Celsius
pub value: f32,
/// Unix epoch timestamp in seconds (i32 to match RTI IDL 'long')
pub timestamp: i32,
}}
// === DDS trait impl (Task 1.2 - full implementation without proc-macro) ===
impl ::hdds::dds::DDS for Temperature {{
fn type_descriptor() -> &'static ::hdds::core::types::TypeDescriptor {{
static DESC: ::hdds::core::types::TypeDescriptor = ::hdds::core::types::TypeDescriptor {{
type_id: {:#010X},
type_name: \"Temperature\", // v168: Simple name to match RTI IDL (no module)
size_bytes: 8,
alignment: 4,
is_variable_size: false,
fields: &[
::hdds::core::types::FieldLayout {{
name: \"value\",
offset_bytes: 0,
field_type: ::hdds::core::types::FieldType::Primitive(::hdds::core::types::PrimitiveKind::F32),
alignment: 4,
size_bytes: 4,
element_type: None,
}},
::hdds::core::types::FieldLayout {{
name: \"timestamp\",
offset_bytes: 4,
field_type: ::hdds::core::types::FieldType::Primitive(::hdds::core::types::PrimitiveKind::I32), // v64: Match RTI IDL 'long'
alignment: 4,
size_bytes: 4,
element_type: None,
}},
],
}};
&DESC
}}
fn encode_cdr2(&self, buf: &mut [u8]) -> ::hdds::dds::Result<usize> {{
use ::hdds::core::ser::Cdr2Encode;
self.encode_cdr2_le(buf).map_err(Into::into)
}}
fn decode_cdr2(buf: &[u8]) -> ::hdds::dds::Result<Self> {{
use ::hdds::core::ser::Cdr2Decode;
Self::decode_cdr2_le(buf).map(|(val, _)| val).map_err(Into::into)
}}
fn get_type_object() -> Option<::hdds::xtypes::CompleteTypeObject> {{
Some(::hdds::xtypes::CompleteTypeObject::Struct(
::hdds::xtypes::CompleteStructType {{
struct_flags: ::hdds::xtypes::StructTypeFlag::IS_FINAL,
header: ::hdds::xtypes::CompleteStructHeader {{
base_type: None,
detail: ::hdds::xtypes::CompleteTypeDetail::new(\"Temperature\"), // v168: Simple name
}},
member_seq: vec![
::hdds::xtypes::CompleteStructMember {{
common: ::hdds::xtypes::CommonStructMember {{
member_id: 0,
member_flags: ::hdds::xtypes::MemberFlag::empty(),
member_type_id: ::hdds::xtypes::TypeIdentifier::TK_FLOAT32,
}},
detail: ::hdds::xtypes::CompleteMemberDetail::new(\"value\"),
}},
::hdds::xtypes::CompleteStructMember {{
common: ::hdds::xtypes::CommonStructMember {{
member_id: 1,
member_flags: ::hdds::xtypes::MemberFlag::empty(),
member_type_id: ::hdds::xtypes::TypeIdentifier::TK_INT32, // v64: Match RTI IDL 'long'
}},
detail: ::hdds::xtypes::CompleteMemberDetail::new(\"timestamp\"),
}},
],
}}
))
}}
}}
// === CDR2 low-level encode/decode impls (manual, Phase 7a) ===
impl Cdr2Encode for Temperature {{
fn encode_cdr2_le(&self, buf: &mut [u8]) -> Result<usize, hdds::core::ser::CdrError> {{
use hdds::core::ser::{{CursorMut, CdrError}};
let mut cursor = CursorMut::new(buf);
cursor.write_bytes(&self.value.to_le_bytes())
.map_err(|_| CdrError::BufferTooSmall)?;
cursor.write_bytes(&self.timestamp.to_le_bytes())
.map_err(|_| CdrError::BufferTooSmall)?;
Ok(cursor.offset())
}}
fn max_cdr2_size(&self) -> usize {{
8 // Fixed size: f32 (4) + i32 (4)
}}
}}
impl Cdr2Decode for Temperature {{
fn decode_cdr2_le(buf: &[u8]) -> Result<(Self, usize), hdds::core::ser::CdrError> {{
use hdds::core::ser::{{Cursor, CdrError}};
let mut cursor = Cursor::new(buf);
let value_bytes = cursor.read_bytes(4)
.map_err(|_| CdrError::UnexpectedEof)?;
let mut value_arr = [0u8; 4];
value_arr.copy_from_slice(value_bytes);
let value = f32::from_le_bytes(value_arr);
let timestamp_bytes = cursor.read_bytes(4)
.map_err(|_| CdrError::UnexpectedEof)?;
let mut timestamp_arr = [0u8; 4];
timestamp_arr.copy_from_slice(timestamp_bytes);
let timestamp = i32::from_le_bytes(timestamp_arr); // v64: Match RTI IDL 'long'
Ok((Self {{ value, timestamp }}, cursor.offset()))
}}
}}
", type_id);
let mut file = fs::File::create(&output_path)?;
file.write_all(code.as_bytes())?;
println!("cargo:warning=Generated src/generated/temperature.rs");
Ok(())
}
fn compute_fnv1a_hash(s: &str) -> u32 {
let mut hash = 2_166_136_261_u32;
for byte in s.bytes() {
hash ^= u32::from(byte);
hash = hash.wrapping_mul(16_777_619);
}
hash
}
fn generate_mod_rs(output_dir: &Path) -> std::io::Result<()> {
let output_path = output_dir.join("mod.rs");
let code = "\
// SPDX-License-Identifier: Apache-2.0 OR MIT
// Copyright (c) 2025-2026 naskel.com
//
// Auto-generated by build.rs
// DO NOT EDIT - Changes will be overwritten on next build
//
// This module aggregates all generated types from IDL definitions.
// Phase 7a: temperature (hardcoded MVP)
// Phase 8: Full IDL parser integration
pub mod temperature {
include!(concat!(env!(\"OUT_DIR\"), \"/generated/temperature.rs\"));
}
";
let mut file = fs::File::create(&output_path)?;
file.write_all(code.as_bytes())?;
println!("cargo:warning=Generated src/generated/mod.rs");
Ok(())
}