소스 검색

Align CMake and use new parameters for types generator

Stefan Profanter 6 년 전
부모
커밋
3ea21ea597
4개의 변경된 파일272개의 추가작업 그리고 68개의 파일을 삭제
  1. 41 24
      CMakeLists.txt
  2. 94 44
      tools/generate_datatypes.py
  3. 0 0
      tools/nodeset_compiler/__init__.py
  4. 137 0
      tools/nodeset_compiler/opaque_type_mapping.py

+ 41 - 24
CMakeLists.txt

@@ -90,6 +90,8 @@ mark_as_advanced(UA_ENABLE_DETERMINISTIC_RNG)
 
 option(UA_ENABLE_GENERATE_NAMESPACE0 "Generate and load UA XML Namespace 0 definition (experimental)" OFF)
 mark_as_advanced(UA_ENABLE_GENERATE_NAMESPACE0)
+set(UA_DATATYPES_FILE ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_minimal.txt CACHE FILEPATH "File containing the list of datatypes added to the server")
+mark_as_advanced(UA_DATATYPES_FILE)
 
 option(UA_ENABLE_VALGRIND_UNIT_TESTS "Use Valgrind to detect memory leaks when running the unit tests" OFF)
 mark_as_advanced(UA_ENABLE_VALGRIND_UNIT_TESTS)
@@ -380,26 +382,32 @@ endif()
 # Generate source files #
 #########################
 
-# standard data types
+if (UA_DATATYPES_FILE STREQUAL "")
+    set(SELECTED_TYPES_TMP "")
+else()
+    set(SELECTED_TYPES_TMP "--selected-types=${UA_DATATYPES_FILE}")
+endif()
+
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.c
                           ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.h
                           ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_handling.h
                           ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_encoding_binary.h
                    PRE_BUILD
                    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_datatypes.py
-                           --typedescriptions ${PROJECT_SOURCE_DIR}/tools/schema/NodeIds.csv
-                           --selected_types=${PROJECT_SOURCE_DIR}/tools/schema/datatypes_minimal.txt
-                           ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd ${PROJECT_BINARY_DIR}/src_generated/ua_types
+                           --type-csv=${PROJECT_SOURCE_DIR}/tools/schema/NodeIds.csv
+                           ${SELECTED_TYPES_TMP}
+                           --type-bsd=${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd
+                           ${PROJECT_BINARY_DIR}/src_generated/ua_types
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_datatypes.py
-                           ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_minimal.txt
-                           ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd
-                           ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/NodeIds.csv)
+                           ${PROJECT_SOURCE_DIR}/tools/schema/NodeIds.csv
+                           ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd
+                           ${SELECTED_TYPES})
 # we need a custom target to avoid that the generator is called concurrently and thus overwriting files while the other thread is compiling
 add_custom_target(open62541-generator-types DEPENDS
-        ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.c
-        ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.h
-        ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_handling.h
-        ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_encoding_binary.h)
+                  ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.c
+                  ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated.h
+                  ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_handling.h
+                  ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_encoding_binary.h)
 
 # transport data types
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_transport_generated.c
@@ -408,13 +416,16 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_transport_gener
                           ${PROJECT_BINARY_DIR}/src_generated/ua_transport_generated_encoding_binary.h
                    PRE_BUILD
                    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/generate_datatypes.py
-                           --namespace=1 --selected_types=${PROJECT_SOURCE_DIR}/tools/schema/datatypes_transport.txt
-                           ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd
-                           ${PROJECT_SOURCE_DIR}/tools/schema/Custom.Opc.Ua.Transport.bsd
+                           --namespace=1
+                           --selected-types=${PROJECT_SOURCE_DIR}/tools/schema/datatypes_transport.txt
+                           --type-bsd=${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd
+                           --type-bsd=${PROJECT_SOURCE_DIR}/tools/schema/Custom.Opc.Ua.Transport.bsd
+                           --no-builtin
                            ${PROJECT_BINARY_DIR}/src_generated/ua_transport
                    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_datatypes.py
                            ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_transport.txt
-                           ${CMAKE_CURRENT_SOURCE_DIR}/tools/schema/Custom.Opc.Ua.Transport.bsd)
+                           ${PROJECT_SOURCE_DIR}/tools/schema/Opc.Ua.Types.bsd
+                           ${PROJECT_SOURCE_DIR}/tools/schema/Custom.Opc.Ua.Transport.bsd)
 # we need a custom target to avoid that the generator is called concurrently and thus overwriting files while the other thread is compiling
 add_custom_target(open62541-generator-transport DEPENDS
         ${PROJECT_BINARY_DIR}/src_generated/ua_transport_generated.c
@@ -446,13 +457,15 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/open62541.c
                    COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py
                            ${OPEN62541_VER_COMMIT} ${CMAKE_CURRENT_BINARY_DIR}/open62541.c
                            ${internal_headers} ${lib_sources} ${default_plugin_sources}
-                   DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py ${internal_headers} ${lib_sources})
+                   DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py ${internal_headers}
+                           ${lib_sources})
 
 add_custom_target(open62541-amalgamation-source DEPENDS ${PROJECT_BINARY_DIR}/open62541.c)
 add_custom_target(open62541-amalgamation-header DEPENDS ${PROJECT_BINARY_DIR}/open62541.h)
 
-add_dependencies(open62541-amalgamation-source open62541-generator-types open62541-generator-transport open62541-generator-statuscode)
 add_dependencies(open62541-amalgamation-header open62541-generator-types)
+add_dependencies(open62541-amalgamation-source open62541-generator-types
+                 open62541-generator-transport open62541-generator-statuscode)
 
 # generated namespace 0
 add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src_generated/ua_namespaceinit_generated.c
@@ -492,8 +505,12 @@ if(UA_ENABLE_AMALGAMATION)
     # and thus may overwrite the amalgamation result during multiprocessor compilation
     # the header is already a dependency of open62541 target itself
     add_dependencies(open62541-object
-            open62541-amalgamation-header
-            open62541-amalgamation-source)
+                     open62541-amalgamation-header
+                     open62541-generator-types
+                     open62541-generator-transport
+                     open62541-generator-statuscode
+                     open62541-amalgamation-source
+                     )
 
     add_library(open62541 $<TARGET_OBJECTS:open62541-object>)
 
@@ -504,14 +521,14 @@ else()
     add_definitions(-DUA_NO_AMALGAMATION)
     add_library(open62541-object OBJECT ${lib_sources} ${internal_headers} ${exported_headers})
     add_dependencies(open62541-object
-            open62541-amalgamation-header
-            open62541-generator-types
-            open62541-generator-transport
-            open62541-generator-statuscode)
+                     open62541-amalgamation-header
+                     open62541-generator-types
+                     open62541-generator-transport
+                     open62541-generator-statuscode)
     target_include_directories(open62541-object PRIVATE ${PROJECT_SOURCE_DIR}/src)
 
     add_library(open62541-plugins OBJECT ${default_plugin_sources} ${exported_headers})
-    add_dependencies(open62541-plugins open62541-generator-types)
+    add_dependencies(open62541-plugins open62541-generator-types open62541-generator-transport)
     target_include_directories(open62541-plugins PRIVATE ${PROJECT_SOURCE_DIR}/plugins)
     target_include_directories(open62541-plugins PRIVATE ${PROJECT_BINARY_DIR}/src_generated)
     target_compile_definitions(open62541-plugins PRIVATE -DUA_DYNAMIC_LINKING_EXPORT)

+ 94 - 44
tools/generate_datatypes.py

@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this 
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
@@ -12,6 +14,7 @@ import re
 import xml.etree.ElementTree as etree
 import itertools
 import argparse
+from nodeset_compiler.opaque_type_mapping import get_base_type_for_opaque
 
 types = OrderedDict() # contains types that were already parsed
 typedescriptions = {} # contains type nodeids
@@ -61,9 +64,9 @@ class StructMember(object):
         self.isArray = isArray
 
 class Type(object):
-    def __init__(self, outname, xml):
+    def __init__(self, outname, xml, namespace):
         self.name = xml.get("Name")
-        self.ns0 = ("true" if outname == "ua_types" else "false")
+        self.ns0 = ("true" if namespace == 0 else "false")
         self.typeIndex = outname.upper() + "_" + self.name.upper()
         self.outname = outname
         self.description = ""
@@ -170,8 +173,8 @@ class BuiltinType(Type):
             self.members = [StructMember("", self, False)]
 
 class EnumerationType(Type):
-    def __init__(self, outname, xml):
-        Type.__init__(self, outname, xml)
+    def __init__(self, outname, xml, namespace):
+        Type.__init__(self, outname, xml, namespace)
         self.pointerfree = "true"
         self.overlayable = "UA_BINARY_OVERLAYABLE_INTEGER"
         self.members = [StructMember("", types["Int32"], False)] # encoded as uint32
@@ -191,16 +194,17 @@ class EnumerationType(Type):
                                                             " = " + kv[1], values)) + "\n} UA_%s;" % self.name
 
 class OpaqueType(Type):
-    def __init__(self, outname, xml):
-        Type.__init__(self, outname, xml)
-        self.members = [StructMember("", types["ByteString"], False)] # encoded as string
+    def __init__(self, outname, xml, namespace, baseType):
+        Type.__init__(self, outname, xml, namespace)
+        self.baseType = baseType
+        self.members = [StructMember("", types[baseType], False)] # encoded as string
 
     def typedef_h(self):
-        return "typedef UA_ByteString UA_%s;" % self.name
+        return "typedef UA_" + self.baseType + " UA_%s;" % self.name
 
 class StructType(Type):
-    def __init__(self, outname, xml):
-        Type.__init__(self, outname, xml)
+    def __init__(self, outname, xml, namespace):
+        Type.__init__(self, outname, xml, namespace)
         self.members = []
         lengthfields = [] # lengthfields of arrays are not included as members
         for child in xml:
@@ -250,7 +254,7 @@ class StructType(Type):
 # Parse Typedefinitions #
 #########################
 
-def parseTypeDefinitions(outname, xmlDescription):
+def parseTypeDefinitions(outname, xmlDescription, namespace):
     def typeReady(element):
         "Are all member types defined?"
         for child in element:
@@ -286,11 +290,11 @@ def parseTypeDefinitions(outname, xmlDescription):
             if name in builtin_types:
                 types[name] = BuiltinType(name)
             elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
-                types[name] = EnumerationType(outname, typeXml)
+                types[name] = EnumerationType(outname, typeXml, namespace)
             elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
-                types[name] = OpaqueType(outname, typeXml)
+                types[name] = OpaqueType(outname, typeXml, namespace, get_base_type_for_opaque(name)['name'])
             elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
-                types[name] = StructType(outname, typeXml)
+                types[name] = StructType(outname, typeXml, namespace)
             else:
                 raise Exception("Type not known")
             del snippets[name]
@@ -307,10 +311,9 @@ class TypeDescription(object):
         self.xmlEncodingId = "0"
         self.binaryEncodingId = "0"
 
-def parseTypeDescriptions(filename, namespaceid):
+def parseTypeDescriptions(f, namespaceid):
     definitions = {}
-    with open(filename) as f:
-        input_str = f.read()
+    input_str = f.read()
     input_str = input_str.replace('\r','')
     rows = map(lambda x:tuple(x.split(',')), input_str.split('\n'))
     for index, row in enumerate(rows):
@@ -336,27 +339,69 @@ def parseTypeDescriptions(filename, namespaceid):
             definitions["ExtensionObject"] = TypeDescription(row[0], row[1], namespaceid)
         elif row[0] not in types:
             continue
-        elif type(types[row[0]]) == EnumerationType:
-            definitions[row[0]] = TypeDescription(row[0], "6", namespaceid) # enumerations look like int32 on the wire
         else:
             definitions[row[0]] = TypeDescription(row[0], row[1], namespaceid)
     return definitions
 
+def merge_dicts(*dict_args):
+    """
+    Given any number of dicts, shallow copy and merge into a new dict,
+    precedence goes to key value pairs in latter dicts.
+    """
+    result = {}
+    for dictionary in dict_args:
+        result.update(dictionary)
+    return result
+
 ###############################
 # Parse the Command Line Input#
 ###############################
 
 parser = argparse.ArgumentParser()
-parser.add_argument('--typedescriptions', help='csv file with type descriptions')
-parser.add_argument('--namespace', type=int, default=0, help='namespace id of the generated type nodeids (defaults to 0)')
-parser.add_argument('--selected_types', help='file with list of types (among those parsed) to be generated')
-parser.add_argument('typexml_ns0', help='path/to/Opc.Ua.Types.bsd ...')
-parser.add_argument('typexml_additional', nargs='*', help='path/to/Opc.Ua.Types.bsd ...')
-parser.add_argument('outfile', help='output file w/o extension')
+parser.add_argument('-c', '--type-csv',
+                    metavar="<typeDescriptions>",
+                    type=argparse.FileType('r'),
+                    dest="type_csv",
+                    action='append',
+                    default=[],
+                    help='csv file with type descriptions')
+
+parser.add_argument('--namespace',
+                    type=int,
+                    dest="namespace",
+                    default=0,
+                    help='namespace id of the generated type nodeids (defaults to 0)')
+
+parser.add_argument('-s', '--selected-types',
+                    metavar="<selectedTypes>",
+                    type=argparse.FileType('r'),
+                    dest="selected_types",
+                    action='append',
+                    default=[],
+                    help='file with list of types (among those parsed) to be generated. If not given, all types are generated')
+
+parser.add_argument('--no-builtin',
+                    action='store_true',
+                    dest="no_builtin",
+                    help='Do not generate builtin types')
+
+parser.add_argument('-t', '--type-bsd',
+                    metavar="<typeBsds>",
+                    type=argparse.FileType('r'),
+                    dest="type_bsd",
+                    action='append',
+                    default=[],
+                    help='csv file with type descriptions')
+
+parser.add_argument('outfile',
+                    metavar='<outputFile>',
+                    help='output file w/o extension')
 args = parser.parse_args()
 
 outname = args.outfile.split("/")[-1]
-inname = ', '.join([args.typexml_ns0.split("/")[-1]] + list(map(lambda x:x.split("/")[-1], args.typexml_additional)))
+inname = ', '.join(list(map(lambda x:x.name.split("/")[-1], args.type_bsd)))
+
+
 
 ################
 # Create Types #
@@ -365,20 +410,19 @@ inname = ', '.join([args.typexml_ns0.split("/")[-1]] + list(map(lambda x:x.split
 for builtin in builtin_types:
     types[builtin] = BuiltinType(builtin)
 
-with open(args.typexml_ns0) as f:
-    parseTypeDefinitions("ua_types", f)
-for typexml in args.typexml_additional:
-    with open(typexml) as f:
-        parseTypeDefinitions(outname, f)
+for f in args.type_bsd:
+    parseTypeDefinitions(outname, f, args.namespace)
+
 
 typedescriptions = {}
-if args.typedescriptions:
-    typedescriptions = parseTypeDescriptions(args.typedescriptions, args.namespace)
+for f in args.type_csv:
+    typedescriptions = merge_dicts(typedescriptions, parseTypeDescriptions(f, args.namespace))
 
-selected_types = types.keys()
-if args.selected_types:
-    with open(args.selected_types) as f:
-        selected_types = list(filter(len, [line.strip() for line in f]))
+selected_types = []
+for f in args.selected_types:
+    selected_types = list(filter(len, [line.strip() for line in f]))
+if len(selected_types) == 0:
+    selected_types = types.keys()
 
 #############################
 # Write out the Definitions #
@@ -403,7 +447,11 @@ def iter_types(v):
         l = list(v.itervalues())
     else:
         l = list(v.values())
-    return filter(lambda t: t.name in selected_types, l)
+    if len(selected_types) > 0:
+        l = filter(lambda t: t.name in selected_types, l)
+    if args.no_builtin:
+        l = filter(lambda t: type(t) != BuiltinType, l)
+    return l
 
 ################
 # Print Header #
@@ -423,15 +471,17 @@ extern "C" {
 #include "ua_types.h"
 ''' + ('#include "ua_types_generated.h"\n' if outname != "ua_types" else ''))
 
+filtered_types = iter_types(types)
+
 printh('''/**
  * Every type is assigned an index in an array containing the type descriptions.
  * These descriptions are used during type handling (copying, deletion,
  * binary encoding, ...). */''')
-printh("#define " + outname.upper() + "_COUNT %s" % (str(len(selected_types))))
+printh("#define " + outname.upper() + "_COUNT %s" % (str(len(filtered_types))))
 printh("extern UA_EXPORT const UA_DataType " + outname.upper() + "[" + outname.upper() + "_COUNT];")
 
 i = 0
-for t in iter_types(types):
+for t in filtered_types:
     printh("\n/**\n * " +  t.name)
     printh(" * " + "^" * len(t.name))
     if t.description == "":
@@ -473,7 +523,7 @@ extern "C" {
 #endif
 ''')
 
-for t in iter_types(types):
+for t in filtered_types:
     printf("\n/* " + t.name + " */")
     printf(t.functions_c())
 
@@ -498,13 +548,13 @@ printc('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '
 #include "''' + outname + '''_generated.h"
 #include "ua_util.h"''')
 
-for t in iter_types(types):
+for t in filtered_types:
     printc("")
     printc("/* " + t.name + " */")
     printc(t.members_c())
 
 printc("const UA_DataType %s[%s_COUNT] = {" % (outname.upper(), outname.upper()))
-for t in iter_types(types):
+for t in filtered_types:
     printc("")
     printc("/* " + t.name + " */")
     printc(t.datatype_c() + ",")
@@ -521,7 +571,7 @@ printe('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '
 #include "ua_types_encoding_binary.h"
 #include "''' + outname + '''_generated.h"''')
 
-for t in iter_types(types):
+for t in filtered_types:
     printe("\n/* " + t.name + " */")
     printe(t.encoding_h())
 

+ 0 - 0
tools/nodeset_compiler/__init__.py


+ 137 - 0
tools/nodeset_compiler/opaque_type_mapping.py

@@ -0,0 +1,137 @@
+
+# Opaque types are in general defined as simple byte strings. For the base opaque types there is a corresponding node id definition
+# in the nodeset. E.g. Opc.Ua.Types.bsd contains the simple definition for OpaqueType LocaleId. In Opc.Ua.NodeSet2.xml the LocaleId
+# is defined as a Subtype of String(i=12) thus LocaleId is a String object.
+# TODO we can automate this mapping by loading the NodeSet2.xml and read those mappings automatically. For now we just use this map
+opaque_type_mapping = {
+    'Image': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'Number': {
+        'ns': 0,
+        'id': 24,
+        'name': 'BaseDataType'
+    },
+    'UInteger': {
+        'ns': 0,
+        'id': 24,
+        'name': 'BaseDataType'
+    },
+    'ImageBMP': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'ImageGIF': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'ImageJPG': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'ImagePNG': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'BitFieldMaskDataType': {
+        'ns': 0,
+        'id': 9,
+        'name': 'UInt64'
+    },
+    'NormalizedString': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'DecimalString': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'DurationString': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'TimeString': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'DateString': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'Duration': {
+        'ns': 0,
+        'id': 11,
+        'name': 'Double'
+    },
+    'UtcTime': {
+        'ns': 0,
+        'id': 13,
+        'name': 'DateTime'
+    },
+    'LocaleId': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'IntegerId': {
+        'ns': 0,
+        'id': 7,
+        'name': 'UInt32'
+    },
+    'ApplicationInstanceCertificate': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'SessionAuthenticationToken': {
+        'ns': 0,
+        'id': 17,
+        'name': 'NodeId'
+    },
+    'ContinuationPoint': {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    },
+    'Counter': {
+        'ns': 0,
+        'id': 7,
+        'name': 'UInt32'
+    },
+    'NumericRange': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'Time': {
+        'ns': 0,
+        'id': 12,
+        'name': 'String'
+    },
+    'Date': {
+        'ns': 0,
+        'id': 13,
+        'name': 'DateTime'
+    }
+}
+
+def get_base_type_for_opaque(opaqueTypeName):
+    if opaqueTypeName in opaque_type_mapping:
+        return opaque_type_mapping[opaqueTypeName]
+    # Default if not in mapping is ByteString
+    return {
+        'ns': 0,
+        'id': 15,
+        'name': 'ByteString'
+    }