# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A tool for generating IDL files from a template, with distinct user and
system identities for interfaces that are decorated with `BEGIN_INTERFACE` and
`END_INTERFACE`.
`BEGIN_INTERFACE` takes the placeholder guid, the interface that needs distinct
identities, as well as any other items that need to be distinct for `user` and
`system` respectively.
`addToLibrary` specifies the interfaces that should be listed in the `library`
section of the generated IDL. For instance:
* `"addToLibrary": ["user", "system"]` will add the `IInterfaceUser` and
the `IInterfaceSystem` to the `library` section.
* `"addToLibrary": ["", "user", "system"]` will add `IInterface`,
`IInterfaceUser` and `IInterfaceSystem` to the `library` section.
* `"addToLibrary": ["user"]` will add only `IInterfaceUser` to the
`library` section.
`includeFor` is an optional qualifier that indicates what scope the interface
and the specializations should be included for, when used in conjunction with
the optional `--generate_for` command line option. `includeFor` is ignored if
no `--generate_for` command line option is specified. For instance:
* `"includeFor": ["system"]` will add the interface and the specializations only
when using the `--generate_for=system` command line option, but not when using
the `--generate_for=user` command line option.
Here is an example:
```
BEGIN_INTERFACE(
{
"uuid": {
"user":"PLACEHOLDER-GUID-9AD1A645-5A4B-4D36-BC21-F0059482E6EA",
"system":"PLACEHOLDER-GUID-E2BD9A6B-0A19-4C89-AE8B-B7E9E51D9A07"
},
"tokensToSuffix": ["ICompleteStatus"],
"addToLibrary": ["", "user", "system"]
}
)
[
uuid(PLACEHOLDER-GUID-2FCD14AF-B645-4351-8359-E80A0E202A0B),
oleautomation,
pointer_default(unique)
]
interface ICompleteStatus : IUnknown {
[propget] HRESULT statusCode([out, retval] LONG*);
[propget] HRESULT statusMessage([out, retval] BSTR*);
};
END_INTERFACE
[
...
]
library UpdaterLib {
INTERFACES_IN_LIBRARY;
};
```
The example input above will produce the following output in the IDL:
```
[
uuid(PLACEHOLDER-GUID-2FCD14AF-B645-4351-8359-E80A0E202A0B),
oleautomation,
pointer_default(unique)
]
interface ICompleteStatus : IUnknown {
[propget] HRESULT statusCode([out, retval] LONG*);
[propget] HRESULT statusMessage([out, retval] BSTR*);
};
[
uuid(PLACEHOLDER-GUID-9AD1A645-5A4B-4D36-BC21-F0059482E6EA),
oleautomation,
pointer_default(unique)
]
interface ICompleteStatusUser : IUnknown {
[propget] HRESULT statusCode([out, retval] LONG*);
[propget] HRESULT statusMessage([out, retval] BSTR*);
};
[
uuid(PLACEHOLDER-GUID-E2BD9A6B-0A19-4C89-AE8B-B7E9E51D9A07),
oleautomation,
pointer_default(unique)
]
interface ICompleteStatusSystem : IUnknown {
[propget] HRESULT statusCode([out, retval] LONG*);
[propget] HRESULT statusMessage([out, retval] BSTR*);
};
[
...
]
library UpdaterLib {
interface ICompleteStatus;
interface ICompleteStatusUser;
interface ICompleteStatusSystem;
};
```
Usage:
python3 generate_user_system_idl.py --idl_template_file updater_idl.template
--idl_output_file updater_idl_new.idl
"""
import argparse
import json
import re
def _GenerateIDLFile(idl_template_filename, idl_output_filename, generate_for):
pattern = re.compile(
r'''BEGIN_INTERFACE\(
(.*?) # Group for the replacement dictionary.
\)
(.*?) # Group for interface text.
END_INTERFACE''', re.DOTALL | re.X)
with open(idl_template_filename, 'rt') as f:
matches = re.split(pattern, f.read())
# Copy anything before the first 'BEGIN_INTERFACE' to output.
idl_output = [matches[0]]
interfaces_in_library = []
for i in range(1, len(matches), 3):
replacement_dict = json.loads(matches[i])
interface_text = matches[i + 1]
trailer = matches[i + 2]
interface_base_name = re.search(r'interface (\w+) :',
interface_text).group(1)
if not generate_for or generate_for in replacement_dict.get(
'includeFor',
{}) or generate_for in replacement_dict['addToLibrary']:
idl_output.append(interface_text)
if "" in replacement_dict['addToLibrary']:
interfaces_in_library.append("interface " +
interface_base_name)
for scope, placeholder_guid in replacement_dict.get(
'uuid', {}).items():
if generate_for and generate_for != scope:
continue
interfaces_in_library.append("interface " +
interface_base_name +
scope.title())
interface_gen = re.sub(r'(uuid\().*?(\))',
r'\1%s\2' % placeholder_guid,
interface_text)
for k in replacement_dict['tokensToSuffix']:
interface_gen = re.sub(r'\b%s\b' % k,
k + scope.title(),
interface_gen)
idl_output.append(interface_gen)
if trailer.strip():
trailer = re.sub(r'INTERFACES_IN_LIBRARY',
';\n '.join(interfaces_in_library), trailer)
idl_output.append(trailer)
with open(idl_output_filename, 'w') as f_out:
f_out.write('// *** AUTO-GENERATED IDL FILE. ***\n\n')
for item in idl_output:
f_out.write(item)
def _Main():
"""Generates IDL files from a template for user and system marshaling."""
cmd_parser = argparse.ArgumentParser(
description='Tool to generate IDL from template.')
cmd_parser.add_argument('--idl_template_file',
dest='idl_template_file',
type=str,
required=True,
help='Input IDL template file.')
cmd_parser.add_argument('--idl_output_file',
type=str,
required=True,
help='Output IDL file.')
cmd_parser.add_argument('--generate_for',
type=str,
help='Generate the IDL for `user` or `system`.')
flags = cmd_parser.parse_args()
_GenerateIDLFile(flags.idl_template_file, flags.idl_output_file,
flags.generate_for)
if __name__ == '__main__':
_Main()