Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions async-opcua-codegen/sample_codegen_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ targets:
# This is useful if the nodeset lacks node ID CSV files, or those files are incomplete.
node_ids_from_nodeset: false

dependent_nodesets:
# This can be a path, filename, or primary namespace URI.
- file: Another.Namespace.Uri
import_path: crate::generated::another_namespace

# This target generates code to generate nodes that are added to the server address space.
# Each node in the NodeSet2 file creates a function, which is then called from
# a large iterator that can be used as a a node set source.
Expand Down
2 changes: 2 additions & 0 deletions async-opcua-codegen/src/input/nodeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub struct RawEncodingIds {
#[derive(Debug, Clone)]
pub struct TypeInfo {
pub name: String,
pub namespace: String,
pub is_abstract: bool,
pub definition: Option<DataTypeDefinition>,
pub encoding_ids: RawEncodingIds,
Expand Down Expand Up @@ -346,6 +347,7 @@ impl NodeSetInput {
is_abstract: data_type.base.is_abstract,
definition: data_type.definition.clone(),
encoding_ids,
namespace: self.uri.clone(),
},
);
}
Expand Down
49 changes: 33 additions & 16 deletions async-opcua-codegen/src/types/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ pub struct CodeGenerator {
target_namespace: String,
native_types: HashSet<String>,
id_path: String,
namespace_to_import_path: HashMap<String, String>,
}

impl CodeGenerator {
#[allow(clippy::too_many_arguments)]
pub fn new(
external_import_map: HashMap<String, ExternalType>,
native_types: HashSet<String>,
Expand All @@ -106,6 +108,7 @@ impl CodeGenerator {
config: CodeGenItemConfig,
target_namespace: String,
id_path: String,
namespace_to_import_path: HashMap<String, String>,
) -> Self {
Self {
import_map: external_import_map
Expand All @@ -119,7 +122,10 @@ impl CodeGenerator {
Some("ExtensionObject" | "OptionSet") => {
Some(FieldType::ExtensionObject(None))
}
Some(t) => Some(FieldType::Normal(t.to_owned())),
Some(t) => Some(FieldType::Normal {
name: t.to_owned(),
namespace: None,
}),
None => None,
},
path: v.path,
Expand All @@ -137,6 +143,7 @@ impl CodeGenerator {
target_namespace,
native_types,
id_path,
namespace_to_import_path,
}
}

Expand All @@ -159,7 +166,7 @@ impl CodeGenerator {
}

let Some(it) = self.import_map.get(name) else {
// Not in the import map means it's a builtin, we assume these have defaults for now.
// Not in the import map means it's a builtin or external reference, we assume these have defaults for now.
return true;
};

Expand All @@ -175,8 +182,8 @@ impl CodeGenerator {
LoadedType::Struct(s) => {
for k in &s.fields {
let has_default = match &k.typ {
StructureFieldType::Field(FieldType::Normal(f)) => {
self.is_default_recursive(f)
StructureFieldType::Field(FieldType::Normal { name, .. }) => {
self.is_default_recursive(name)
}
StructureFieldType::Array(_) | StructureFieldType::Field(_) => true,
};
Expand Down Expand Up @@ -279,7 +286,7 @@ impl CodeGenerator {
}

/// Get the fully qualified path of a type, by looking it up in the import map.
fn get_type_path(&self, name: &str) -> String {
fn get_type_path(&self, name: &str, namespace: Option<&str>) -> String {
// Type is known, use the external path.
if let Some(ext) = self.import_map.get(name) {
return format!("{}::{}", ext.path, name);
Expand All @@ -288,6 +295,12 @@ impl CodeGenerator {
if self.native_types.contains(name) {
return name.to_owned();
}

if let Some(namespace) = namespace {
if let Some(import_path) = self.namespace_to_import_path.get(namespace) {
return format!("{}::{}", import_path, name);
}
}
// Assume the type is a builtin.
format!("opcua::types::{name}")
}
Expand Down Expand Up @@ -548,7 +561,7 @@ impl CodeGenerator {
fn is_extension_object(&self, typ: Option<&FieldType>) -> bool {
let name = match &typ {
Some(FieldType::Abstract(_)) | Some(FieldType::ExtensionObject(_)) => return true,
Some(FieldType::Normal(s)) => s,
Some(FieldType::Normal { name, .. }) => name,
None => return false,
};
let name = match name.split_once(":") {
Expand Down Expand Up @@ -596,18 +609,22 @@ impl CodeGenerator {

for field in item.visible_fields() {
let typ: Type = match &field.typ {
StructureFieldType::Field(f) => {
syn::parse_str(&self.get_type_path(f.as_type_str())).map_err(|e| {
CodeGenError::from(e)
.with_context(format!("Generating path for {}", f.as_type_str()))
})?
}
StructureFieldType::Field(f) => syn::parse_str(
&self.get_type_path(f.as_type_str(), f.namespace()),
)
.map_err(|e| {
CodeGenError::from(e)
.with_context(format!("Generating path for {}", f.as_type_str()))
})?,
StructureFieldType::Array(f) => {
let path: Path =
syn::parse_str(&self.get_type_path(f.as_type_str())).map_err(|e| {
CodeGenError::from(e)
.with_context(format!("Generating path for {}", f.as_type_str()))
})?;
syn::parse_str(&self.get_type_path(f.as_type_str(), f.namespace()))
.map_err(|e| {
CodeGenError::from(e).with_context(format!(
"Generating path for {}",
f.as_type_str()
))
})?;
parse_quote! { Option<Vec<#path>> }
}
};
Expand Down
10 changes: 8 additions & 2 deletions async-opcua-codegen/src/types/loaders/binary_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ impl<'a> BsdTypeLoader<'a> {
fn get_field_type(field: &str) -> FieldType {
match field {
"ExtensionObject" | "OptionSet" => FieldType::ExtensionObject(None),
_ => FieldType::Normal(field.to_owned()),
_ => FieldType::Normal {
name: field.to_owned(),
namespace: None,
},
}
}

Expand Down Expand Up @@ -114,7 +117,10 @@ impl<'a> BsdTypeLoader<'a> {
Some("ua:ExtensionObject" | "ua:OptionSet") => {
Some(FieldType::ExtensionObject(None))
}
Some(base) => Some(FieldType::Normal(self.massage_type_name(base))),
Some(base) => Some(FieldType::Normal {
name: self.massage_type_name(base),
namespace: Some(self.target_namespace()),
}),
None => None,
},
is_union: false,
Expand Down
8 changes: 6 additions & 2 deletions async-opcua-codegen/src/types/loaders/nodeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use opcua_xml::schema::ua_node_set::{DataTypeField, LocalizedText, UADataType, U
use crate::{
input::{NodeSetInput, SchemaCache, TypeInfo},
utils::{split_qualified_name, to_snake_case, NodeIdVariant, ParsedNodeId},
CodeGenError,
CodeGenError, BASE_NAMESPACE,
};

use super::{
Expand Down Expand Up @@ -67,7 +67,10 @@ impl<'a> NodeSetTypeLoader<'a> {
} else if info.name == "Structure" || info.name == "OptionSet" {
FieldType::ExtensionObject(Some(info.encoding_ids))
} else {
FieldType::Normal(info.name)
FieldType::Normal {
name: info.name,
namespace: Some(info.namespace),
}
}
}

Expand Down Expand Up @@ -283,6 +286,7 @@ impl<'a> NodeSetTypeLoader<'a> {
is_abstract: false,
definition: None,
encoding_ids: Default::default(),
namespace: BASE_NAMESPACE.to_owned(),
})
} else {
Ok(r)
Expand Down
14 changes: 12 additions & 2 deletions async-opcua-codegen/src/types/loaders/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,24 @@ pub struct StructureField {
pub enum FieldType {
Abstract(#[allow(unused)] String),
ExtensionObject(Option<RawEncodingIds>),
Normal(String),
Normal {
name: String,
namespace: Option<String>,
},
}

impl FieldType {
pub fn as_type_str(&self) -> &str {
match self {
FieldType::Abstract(_) | FieldType::ExtensionObject(_) => "ExtensionObject",
FieldType::Normal(s) => s,
FieldType::Normal { name, .. } => name,
}
}

pub fn namespace(&self) -> Option<&str> {
match self {
FieldType::Normal { namespace, .. } => namespace.as_deref(),
_ => None,
}
}
}
Expand Down
19 changes: 16 additions & 3 deletions async-opcua-codegen/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use tracing::info;

use crate::{
input::{BinarySchemaInput, NodeSetInput, SchemaCache},
CodeGenError, BASE_NAMESPACE,
CodeGenError, DependentNodeset, BASE_NAMESPACE,
};

#[derive(Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -60,6 +60,9 @@ pub struct TypeCodeGenTarget {
#[serde(default)]
/// If true, instead of using `id_path` and ID enums, generate the node IDs from the nodeset file.
pub node_ids_from_nodeset: bool,
/// List of dependent nodesets to load types from. Only valid when using a NodeSet input.
#[serde(default)]
pub dependent_nodesets: Vec<DependentNodeset>,
}

impl Default for TypeCodeGenTarget {
Expand All @@ -75,6 +78,7 @@ impl Default for TypeCodeGenTarget {
extra_header: String::new(),
id_path: defaults::id_path(),
node_ids_from_nodeset: false,
dependent_nodesets: Vec::new(),
}
}
}
Expand Down Expand Up @@ -120,7 +124,7 @@ pub fn generate_types(
.map_err(|e| e.in_file(&input.path))?;
info!("Loaded {} types", types.len());

generate_types_inner(target, target_namespace, types)
generate_types_inner(target, target_namespace, types, HashMap::new())
}

/// Generate types from the given NodeSet file input.
Expand Down Expand Up @@ -149,13 +153,21 @@ pub fn generate_types_nodeset(
let types = type_loader.load_types(cache)?;
info!("Loaded {} types", types.len());

generate_types_inner(target, target_namespace, types)
let mut namespace_to_import_path = HashMap::new();
for dependent_nodeset in &target.dependent_nodesets {
let dep_input = cache.get_nodeset(&dependent_nodeset.file)?;
namespace_to_import_path
.insert(dep_input.uri.clone(), dependent_nodeset.import_path.clone());
}

generate_types_inner(target, target_namespace, types, namespace_to_import_path)
}

fn generate_types_inner(
target: &TypeCodeGenTarget,
target_namespace: String,
types: Vec<LoadedType>,
namespace_to_import_path: HashMap<String, String>,
) -> Result<(Vec<GeneratedItem>, String), CodeGenError> {
let mut types_import_map = basic_types_import_map();
for (k, v) in &target.types_import_map {
Expand All @@ -179,6 +191,7 @@ fn generate_types_inner(
},
target_namespace.clone(),
target.id_path.clone(),
namespace_to_import_path,
);

Ok((generator.generate_types()?, target_namespace))
Expand Down
34 changes: 25 additions & 9 deletions codegen-tests/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,34 @@ use opcua_codegen::{CodeGenConfig, CodeGenSource, CodeGenTarget, TypeCodeGenTarg
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let target_dir = format!("{}/opcua_generated", out_dir);
println!("cargo:rerun-if-changed=schemas/Async.Opcua.Test.NodeSet2.xml");
println!("cargo:rerun-if-changed=schemas/Async.Opcua.Test.Ext.NodeSet2.xml");
println!("cargo:rustc-env=OPCUA_GENERATED_DIR={}", target_dir);
run_codegen(
&CodeGenConfig {
targets: vec![CodeGenTarget::Types(TypeCodeGenTarget {
file: "Async.Opcua.Test.NodeSet2.xml".to_owned(),
output_dir: target_dir,
enums_single_file: true,
structs_single_file: true,
node_ids_from_nodeset: true,
default_excluded: ["SimpleEnum".to_string()].into_iter().collect(),
..Default::default()
})],
targets: vec![
CodeGenTarget::Types(TypeCodeGenTarget {
file: "Async.Opcua.Test.NodeSet2.xml".to_owned(),
output_dir: format!("{}/base", target_dir),
enums_single_file: true,
structs_single_file: true,
node_ids_from_nodeset: true,
default_excluded: ["SimpleEnum".to_string()].into_iter().collect(),
..Default::default()
}),
CodeGenTarget::Types(TypeCodeGenTarget {
file: "Async.Opcua.Test.Ext.NodeSet2.xml".to_owned(),
output_dir: format!("{}/ext", target_dir),
enums_single_file: true,
structs_single_file: true,
node_ids_from_nodeset: true,
dependent_nodesets: vec![opcua_codegen::DependentNodeset {
file: "Async.Opcua.Test.NodeSet2.xml".to_owned(),
import_path: "crate::generated::base".to_owned(),
}],
..Default::default()
}),
],
sources: vec![
CodeGenSource::Implicit("./schemas".to_owned()),
CodeGenSource::Implicit("../schemas/1.05".to_owned()),
Expand Down
Loading