import re
import sys
import os
def transform_cdraux(content):
"""Transform a CdrAux.ipp file from FastCDR v2 API to v1-compatible code."""
content = content.replace(
'#include <fastcdr/CdrSizeCalculator.hpp>',
'#include "dds_types/util/fastcdr_v1_compat.h"'
)
content = content.replace(
'#include <fastcdr/cdr/fixed_size_string.hpp>',
'// fixed_size_string.hpp removed (FastCDR v2 only, not used)'
)
content = re.sub(
r'\s*\w+\.begin_serialize_type\s*\([^;]*\);\s*\n',
'\n',
content,
flags=re.DOTALL
)
content = re.sub(
r'\s*\w+\.end_serialize_type\s*\([^)]*\);\s*\n',
'\n',
content
)
content = re.sub(
r'\s*eprosima::fastcdr::Cdr::state\s+\w+\s*\(\s*\w+\s*\)\s*;\s*\n',
'\n',
content
)
content = transform_deserialize_type_blocks(content)
content = transform_chained_serialize(content)
content = transform_individual_serialize(content)
content = transform_individual_deserialize(content)
content = make_functions_inline(content)
return content
def make_functions_inline(content):
"""Add 'inline' to all function definitions in .ipp files.
Explicit template specializations and regular functions in .ipp files
have external linkage by default. When the .ipp is included from
multiple translation units, this causes multiple-definition linker errors.
We add 'inline' to:
1. template<> ... eProsima_user_DllExport TYPE FUNC(...) { ... }
2. Regular (non-template) function definitions like void serialize_key(...)
We skip:
- Lines that already have 'inline'
- Pure declarations (no body / no '{' follows)
"""
content = re.sub(
r'^(template\s*<>\s*\n)(eProsima_user_DllExport\s)',
r'\1inline \2',
content,
flags=re.MULTILINE
)
content = re.sub(
r'^(void\s+serialize_key\s*\()',
r'inline \1',
content,
flags=re.MULTILINE
)
return content
def transform_deserialize_type_blocks(content):
"""Find and transform all deserialize_type blocks."""
result = []
pos = 0
while pos < len(content):
match = re.search(
r'(\s*)(\w+)\.deserialize_type\s*\(',
content[pos:]
)
if not match:
result.append(content[pos:])
break
result.append(content[pos:pos + match.start()])
indent = match.group(1)
cdr_var = match.group(2)
start_paren = pos + match.start() + len(match.group(0)) - 1
end_pos = find_matching_close(content, start_paren)
if end_pos < 0:
result.append(content[pos + match.start():])
break
block = content[start_paren:end_pos + 1]
lambda_match = re.search(
r'\[\&\w+\]\s*\(\s*eprosima::fastcdr::Cdr\s*&\s*(\w+)',
block
)
dcdr_var = lambda_match.group(1) if lambda_match else 'dcdr'
field_reads = extract_field_reads(block, dcdr_var, cdr_var)
if field_reads:
result.append('\n')
for read in field_reads:
result.append(f'{indent}{read}\n')
else:
result.append(content[pos + match.start():end_pos + 2])
pos = end_pos + 1
if pos < len(content) and content[pos] == ';':
pos += 1
return ''.join(result)
def find_matching_close(content, open_pos):
"""Find the matching closing paren/brace for the one at open_pos."""
char = content[open_pos]
if char == '(':
close_char = ')'
elif char == '{':
close_char = '}'
else:
return -1
depth = 1
pos = open_pos + 1
while pos < len(content) and depth > 0:
c = content[pos]
if c == char:
depth += 1
elif c == close_char:
depth -= 1
elif c == '"':
pos += 1
while pos < len(content) and content[pos] != '"':
if content[pos] == '\\':
pos += 1
pos += 1
elif c == "'":
pos += 1
while pos < len(content) and content[pos] != "'":
if content[pos] == '\\':
pos += 1
pos += 1
pos += 1
if depth == 0:
return pos - 1
return -1
def extract_field_reads(block, dcdr_var, cdr_var):
"""Extract field reads from the switch/case inside deserialize_type lambda."""
reads = []
simple_cases = re.findall(
r'case\s+\d+\s*:\s*\n?\s*' + re.escape(dcdr_var) + r'\s*>>\s*([^;]+)\s*;\s*\n?\s*break\s*;',
block
)
if simple_cases:
for field_expr in simple_cases:
reads.append(f'eprosima::fastcdr::deserialize({cdr_var}, {field_expr.strip()});')
return reads
brace_cases = re.findall(
r'case\s+\d+\s*:\s*\{([^}]+)\}',
block
)
for case_body in brace_cases:
read_match = re.search(
re.escape(dcdr_var) + r'\s*>>\s*(\w+)\s*;',
case_body
)
assign_match = re.search(
r'(data\.\w+(?:\(\))?)\s*=\s*static_cast<[^>]+>\s*\((\w+)\)',
case_body
)
if read_match and assign_match:
temp_var = read_match.group(1)
field = assign_match.group(1)
cast_expr = re.search(r'static_cast<([^>]+)>', case_body).group(0)
type_match = re.search(r'(\w+)\s+' + re.escape(temp_var) + r'\s*;', case_body)
if type_match:
var_type = type_match.group(1)
reads.append(f'{{ {var_type} _tmp; eprosima::fastcdr::deserialize({cdr_var}, _tmp); {field} = {cast_expr}(_tmp); }}')
else:
reads.append(f'eprosima::fastcdr::deserialize({cdr_var}, {field});')
elif read_match:
reads.append(f'eprosima::fastcdr::deserialize({cdr_var}, {read_match.group(1).strip()});')
if reads:
return reads
all_reads = re.findall(
re.escape(dcdr_var) + r'\s*>>\s*([^;]+)\s*;',
block
)
for r in all_reads:
r = r.strip()
if r and not r.startswith('data') and 'mid' not in r:
reads.append(f'eprosima::fastcdr::deserialize({cdr_var}, {r});')
elif r:
reads.append(f'eprosima::fastcdr::deserialize({cdr_var}, {r});')
return reads
def transform_chained_serialize(content):
"""Transform chained scdr << MemberId(N) << expr patterns.
Input pattern (multi-line chained):
scdr
<< eprosima::fastcdr::MemberId(0) << data.code()
<< eprosima::fastcdr::MemberId(1) << data.msg()
<< eprosima::fastcdr::MemberId(2) << data.data()
;
Output:
eprosima::fastcdr::serialize(scdr, data.code());
eprosima::fastcdr::serialize(scdr, data.msg());
eprosima::fastcdr::serialize(scdr, data.data());
"""
def replace_chain(match):
indent = match.group(1)
cdr_var = match.group(2)
chain_body = match.group(3)
pairs = re.findall(
r'<<\s*eprosima::fastcdr::MemberId\(\d+\)\s*<<\s*(.+?)(?=\s*<<\s*eprosima::fastcdr::MemberId|\s*$)',
chain_body,
re.MULTILINE
)
if not pairs:
return match.group(0)
result_lines = []
for expr in pairs:
expr = expr.strip().rstrip(';').strip()
if expr:
result_lines.append(f'{indent} eprosima::fastcdr::serialize({cdr_var}, {expr});')
return '\n'.join(result_lines) + '\n'
content = re.sub(
r'^([ \t]*)(\w+)\s*\n((?:[ \t]*<<\s*eprosima::fastcdr::MemberId\(\d+\)\s*<<[^\n]+\n)+)[ \t]*;[ \t]*\n',
replace_chain,
content,
flags=re.MULTILINE
)
return content
def transform_individual_serialize(content):
"""Transform individual scdr << expr; statements to serialize() calls.
Transforms:
scdr << data.msg();
To:
eprosima::fastcdr::serialize(scdr, data.msg());
Skips lines that are part of a chain (start with <<) or contain MemberId.
Also skips static_cast<void> lines.
"""
def replace_individual(match):
indent = match.group(1)
cdr_var = match.group(2)
expr = match.group(3).strip()
if 'MemberId' in expr:
return match.group(0)
if 'static_cast<void>' in match.group(0):
return match.group(0)
return f'{indent}eprosima::fastcdr::serialize({cdr_var}, {expr});'
content = re.sub(
r'^([ \t]+)(\w+)\s*<<\s*([^<][^;]*);',
replace_individual,
content,
flags=re.MULTILINE
)
return content
def transform_individual_deserialize(content):
"""Transform individual cdr >> expr; statements to deserialize() calls.
Transforms:
cdr >> data.code();
To:
eprosima::fastcdr::deserialize(cdr, data.code());
"""
def replace_individual(match):
indent = match.group(1)
cdr_var = match.group(2)
expr = match.group(3).strip()
return f'{indent}eprosima::fastcdr::deserialize({cdr_var}, {expr});'
content = re.sub(
r'^([ \t]+)(\w+)\s*>>\s*([^;]+);',
replace_individual,
content,
flags=re.MULTILINE
)
return content
def transform_pubsubtypes(content):
"""Transform a PubSubTypes.cxx file from FastCDR v2 API to v1-compatible code.
The generated PubSubTypes.cxx files use:
ser << *p_type; (calls p_type->serialize(ser) in v1 — but IDL types lack that member)
deser >> *p_type; (calls p_type->deserialize(deser) — same problem)
We replace these with explicit free-function calls that ARE provided by CdrAux.ipp:
eprosima::fastcdr::serialize(ser, *p_type);
eprosima::fastcdr::deserialize(deser, *p_type);
"""
content = content.replace(
'#include <fastcdr/CdrSizeCalculator.hpp>',
'#include "dds_types/util/fastcdr_v1_compat.h"'
)
content = content.replace(
'#include <fastcdr/cdr/fixed_size_string.hpp>',
'// fixed_size_string.hpp removed (FastCDR v2 only, not used)'
)
content = re.sub(
r'(\s+)(\w+)\s*<<\s*\*(\w+)\s*;',
r'\1eprosima::fastcdr::serialize(\2, *\3);',
content
)
content = re.sub(
r'(\s+)(\w+)\s*>>\s*\*(\w+)\s*;',
r'\1eprosima::fastcdr::deserialize(\2, *\3);',
content
)
content = re.sub(
r'(\w+)\.get_serialized_data_length\(\)',
r'\1.getSerializedDataLength()',
content
)
content = re.sub(
r'\s*\w+\.begin_serialize_type\s*\([^;]*\);\s*\n',
'\n',
content,
flags=re.DOTALL
)
content = re.sub(
r'\s*\w+\.end_serialize_type\s*\([^)]*\);\s*\n',
'\n',
content
)
content = re.sub(
r'\s*eprosima::fastcdr::Cdr::state\s+\w+\s*\(\s*\w+\s*\)\s*;\s*\n',
'\n',
content
)
return content
def inject_dependent_ipp_includes(content, filepath, upstream_dirs=None):
"""For a CdrAux.ipp file, detect dependent CdrAux.ipp files and add includes.
When ExampleRpcCdrAux.ipp serializes fields that are complex types (e.g.
ExampleFoo), it calls eprosima::fastcdr::serialize(cdr, data.data()) which
needs the ExampleCommonCdrAux.ipp specialization to be visible.
Detection approach:
1. Find #include "XxxCdrAux.hpp" in this .ipp file
2. Read that .hpp file
3. Find includes like #include "Yyy.hpp" (type definition headers from other IDL files)
4. Check if YyyCdrAux.ipp exists in the same directory or any upstream gencode dir
5. If yes, add #include "YyyCdrAux.ipp" before the existing CdrAux.hpp include
`upstream_dirs` is an optional list of additional gencode directories to
search for dependent CdrAux.ipp files (used when an IDL package depends on
types defined in a separately-generated package, e.g. sensor -> common).
"""
if not filepath.endswith('CdrAux.ipp'):
return content
ipp_dir = os.path.dirname(filepath)
search_dirs = [ipp_dir] + list(upstream_dirs or [])
hpp_match = re.search(r'#include\s+"(\w+CdrAux\.hpp)"', content)
if not hpp_match:
return content
cdraux_hpp_name = hpp_match.group(1)
cdraux_hpp_path = os.path.join(ipp_dir, cdraux_hpp_name)
if not os.path.exists(cdraux_hpp_path):
return content
with open(cdraux_hpp_path, 'r') as f:
hpp_content = f.read()
type_hpp_match = re.search(r'#include\s+"(\w+)\.hpp"', hpp_content)
if not type_hpp_match:
return content
type_hpp_name = type_hpp_match.group(1)
type_hpp_path = os.path.join(ipp_dir, type_hpp_name + '.hpp')
if not os.path.exists(type_hpp_path):
return content
with open(type_hpp_path, 'r') as f:
type_hpp_content = f.read()
dep_includes = re.findall(r'#include\s+"(\w+)\.hpp"', type_hpp_content)
dep_ipp_includes = []
own_ipp_basename = os.path.basename(filepath)
for dep_name in dep_includes:
dep_ipp = dep_name + 'CdrAux.ipp'
if dep_ipp == own_ipp_basename:
continue
for d in search_dirs:
if os.path.exists(os.path.join(d, dep_ipp)):
inc = f'#include "{dep_ipp}"'
if inc not in dep_ipp_includes:
dep_ipp_includes.append(inc)
break
if not dep_ipp_includes:
return content
insert_text = '\n'.join(dep_ipp_includes) + '\n'
content = content.replace(
f'#include "{cdraux_hpp_name}"',
insert_text + f'#include "{cdraux_hpp_name}"'
)
print(f" Injected dependent .ipp includes: {dep_ipp_includes}")
return content
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} [--upstream-dir <dir>]... <file.ipp|.cxx|.hpp> [file2 ...]", file=sys.stderr)
sys.exit(1)
upstream_dirs = []
args = sys.argv[1:]
files = []
i = 0
while i < len(args):
a = args[i]
if a == '--upstream-dir':
if i + 1 >= len(args):
print("Error: --upstream-dir needs a directory argument", file=sys.stderr)
sys.exit(1)
upstream_dirs.append(args[i + 1])
i += 2
else:
files.append(a)
i += 1
for filepath in files:
if not os.path.exists(filepath):
print(f"Warning: {filepath} not found, skipping", file=sys.stderr)
continue
with open(filepath, 'r') as f:
content = f.read()
if filepath.endswith('PubSubTypes.cxx'):
transformed = transform_pubsubtypes(content)
elif filepath.endswith('.hpp'):
transformed = transform_cdraux(content)
elif filepath.endswith('CdrAux.ipp'):
transformed = transform_cdraux(content)
transformed = inject_dependent_ipp_includes(transformed, filepath, upstream_dirs)
else:
transformed = transform_cdraux(content)
with open(filepath, 'w') as f:
f.write(transformed)
print(f"Transformed: {filepath}")
if __name__ == '__main__':
main()