Skip to content

Commit

Permalink
Support EnableIf attribute to conditionally disable definitions.
Browse files Browse the repository at this point in the history
Add support for a new EnableIf attribute to conditionally
enable fields/types in mojom definitions.

In order to do this, mojom parsing and code generation has
been split into two separate GN stages. The mojom bindings
generator has been refactored to separate the logic for
parsing and code generation and the GN mojom template has
been updated to express these two distinct stages.

The parse stage now prunes a mojom's AST - filtering
definitions based on the enabled features. These
intermediate ASTs are then pickled to gen/ to be later
read by the code generation stage.

Bug: 676224
Change-Id: I5fc106b43e8ac48339c63c48f7ce42ba5145d174
Reviewed-on: https://chromium-review.googlesource.com/846399
Commit-Queue: Eve Martin-Jones <evem@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Yuzhu Shen <yzshen@chromium.org>
Reviewed-by: Ken Rockot <rockot@chromium.org>
Reviewed-by: Chris Watkins <watk@chromium.org>
Reviewed-by: Sam McNally <sammc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533796}
  • Loading branch information
Eve Martin-Jones authored and Commit Bot committed Feb 1, 2018
1 parent 78069c5 commit f83c71b
Show file tree
Hide file tree
Showing 5 changed files with 437 additions and 75 deletions.
8 changes: 8 additions & 0 deletions mojo/public/tools/bindings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,14 @@ interesting attributes supported today.
field, enum value, interface method, or method parameter was introduced.
See [Versioning](#Versioning) for more details.

**`[EnableIf=value]`**
: The `EnableIf` attribute is used to conditionally enable definitions when
the mojom is parsed. If the `mojom` target in the GN file does not include
the matching `value` in the list of `enabled_features`, the definition
will be disabled. This is useful for mojom definitions that only make
sense on one platform. Note that the `EnableIf` attribute can only be set
once per definition.

## Generated Code For Target Languages

When the bindings generator successfully processes an input Mojom file, it emits
Expand Down
66 changes: 51 additions & 15 deletions mojo/public/tools/bindings/mojom.gni
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ if (enable_mojom_typemapping) {
# EXPORT macro depends on whether the corresponding IMPL macro is defined,
# per standard practice with Chromium component exports.
#
# enabled_features (optional)
# Definitions in a mojom file can be guarded by an EnableIf attribute. If
# the value specified by the attribute does not match any items in the
# list of enabled_features, the definition will be disabled, with no code
# emitted for it.
#
# The following parameters are used to support the component build. They are
# needed so that bindings which are linked with a component can use the same
# export settings for classes. The first three are for the chromium variant, and
Expand Down Expand Up @@ -331,6 +337,48 @@ template("mojom") {
}
}

if (defined(invoker.sources)) {
parser_target_name = "${target_name}__parser"
action_foreach(parser_target_name) {
script = mojom_generator_script
sources = invoker.sources
outputs = [
"{{source_gen_dir}}/{{source_name_part}}.p",
]
args = [
"parse",
"{{source}}",
"-o",
rebase_path(root_gen_dir, root_build_dir),
"-d",
rebase_path("//", root_build_dir),
]
if (defined(invoker.enabled_features)) {
foreach(enabled_feature, invoker.enabled_features) {
args += [
"--enable_feature",
enabled_feature,
]
}
}
}
}

parsed_target_name = "${target_name}__parsed"
group(parsed_target_name) {
public_deps = []
if (defined(invoker.sources)) {
public_deps += [ ":$parser_target_name" ]
}
foreach(d, all_deps) {
# Resolve the name, so that a target //mojo/something becomes
# //mojo/something:something and we can append the parsed
# suffix to get the mojom dependency name.
full_name = get_label_info("$d", "label_no_toolchain")
public_deps += [ "${full_name}__parsed" ]
}
}

# Generate code that is shared by different variants.
if (defined(invoker.sources)) {
common_generator_args = [
Expand Down Expand Up @@ -436,6 +484,7 @@ template("mojom") {
inputs = mojom_generator_sources
sources = invoker.sources
deps = [
":$parsed_target_name",
"//mojo/public/tools/bindings:precompile_templates",
]
outputs = generator_shared_cpp_outputs
Expand All @@ -445,13 +494,6 @@ template("mojom") {
"-g",
"c++",
]
depfile = "{{source_gen_dir}}/${generator_shared_target_name}_{{source_name_part}}.d"
args += [
"--depfile",
depfile,
"--depfile_target",
"{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
]

if (defined(shared_component_export_macro)) {
args += [
Expand Down Expand Up @@ -667,6 +709,7 @@ template("mojom") {
inputs = mojom_generator_sources
sources = invoker.sources
deps = [
":$parsed_target_name",
":$type_mappings_target_name",
"//mojo/public/tools/bindings:precompile_templates",
]
Expand All @@ -691,14 +734,6 @@ template("mojom") {
bindings_configuration.variant,
]
}
depfile =
"{{source_gen_dir}}/${generator_target_name}_{{source_name_part}}.d"
args += [
"--depfile",
depfile,
"--depfile_target",
"{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc",
]

args += [
"--typemap",
Expand Down Expand Up @@ -989,6 +1024,7 @@ template("mojom") {
sources += invoker.sources
}
deps = [
":$parsed_target_name",
"//mojo/public/tools/bindings:precompile_templates",
]
outputs = generator_js_outputs
Expand Down
146 changes: 86 additions & 60 deletions mojo/public/tools/bindings/mojom_bindings_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


import argparse
import cPickle
import hashlib
import importlib
import json
Expand Down Expand Up @@ -42,6 +43,7 @@ def _GetDirAbove(dirname):
from mojom.generate import translate
from mojom.generate import template_expander
from mojom.generate.generator import AddComputedData
from mojom.parse.conditional_features import RemoveDisabledDefinitions
from mojom.parse.parser import Parse


Expand Down Expand Up @@ -146,7 +148,6 @@ class MojomProcessor(object):
def __init__(self, should_generate):
self._should_generate = should_generate
self._processed_files = {}
self._parsed_files = {}
self._typemap = {}

def LoadTypemaps(self, typemaps):
Expand All @@ -162,19 +163,24 @@ def no_comments(line):
language_map.update(typemap)
self._typemap[language] = language_map

def ProcessFile(self, args, remaining_args, generator_modules, filename):
self._ParseFileAndImports(RelativePath(filename, args.depth),
args.import_directories, [])

return self._GenerateModule(args, remaining_args, generator_modules,
RelativePath(filename, args.depth))

def _GenerateModule(self, args, remaining_args, generator_modules,
rel_filename):
rel_filename, imported_filename_stack):
# Return the already-generated module.
if rel_filename.path in self._processed_files:
return self._processed_files[rel_filename.path]
tree = self._parsed_files[rel_filename.path]

if rel_filename.path in imported_filename_stack:
print "%s: Error: Circular dependency" % rel_filename.path + \
MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
sys.exit(1)

pickle_path = _GetPicklePath(rel_filename, args.output_dir)
try:
with open(pickle_path, "rb") as f:
tree = cPickle.load(f)
except (IOError, cPickle.UnpicklingError) as e:
print "%s: Error: %s" % (pickle_path, str(e))
sys.exit(1)

dirname = os.path.dirname(rel_filename.path)

Expand All @@ -186,7 +192,8 @@ def _GenerateModule(self, args, remaining_args, generator_modules,
RelativePath(dirname, rel_filename.source_root),
parsed_imp.import_filename, args.import_directories)
imports[parsed_imp.import_filename] = self._GenerateModule(
args, remaining_args, generator_modules, rel_import_file)
args, remaining_args, generator_modules, rel_import_file,
imported_filename_stack + [rel_filename.path])

# Set the module path as relative to the source root.
# Normalize to unix-style path here to keep the generators simpler.
Expand Down Expand Up @@ -225,42 +232,6 @@ def _GenerateModule(self, args, remaining_args, generator_modules,
self._processed_files[rel_filename.path] = module
return module

def _ParseFileAndImports(self, rel_filename, import_directories,
imported_filename_stack):
# Ignore already-parsed files.
if rel_filename.path in self._parsed_files:
return

if rel_filename.path in imported_filename_stack:
print "%s: Error: Circular dependency" % rel_filename.path + \
MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
sys.exit(1)

try:
with open(rel_filename.path) as f:
source = f.read()
except IOError as e:
print "%s: Error: %s" % (rel_filename.path, e.strerror) + \
MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
sys.exit(1)

try:
tree = Parse(source, rel_filename.path)
except Error as e:
full_stack = imported_filename_stack + [rel_filename.path]
print str(e) + MakeImportStackMessage(full_stack)
sys.exit(1)

dirname = os.path.split(rel_filename.path)[0]
for imp_entry in tree.import_list:
import_file_entry = FindImportFile(
RelativePath(dirname, rel_filename.source_root),
imp_entry.import_filename, import_directories)
self._ParseFileAndImports(import_file_entry, import_directories,
imported_filename_stack + [rel_filename.path])

self._parsed_files[rel_filename.path] = tree


def _Generate(args, remaining_args):
if args.variant == "none":
Expand All @@ -279,17 +250,55 @@ def _Generate(args, remaining_args):
processor = MojomProcessor(lambda filename: filename in args.filename)
processor.LoadTypemaps(set(args.typemaps))
for filename in args.filename:
processor.ProcessFile(args, remaining_args, generator_modules, filename)
if args.depfile:
assert args.depfile_target
with open(args.depfile, 'w') as f:
f.write('%s: %s' % (
args.depfile_target,
' '.join(processor._parsed_files.keys())))
processor._GenerateModule(args, remaining_args, generator_modules,
RelativePath(filename, args.depth), [])

return 0


def _GetPicklePath(rel_filename, output_dir):
filename, _ = os.path.splitext(rel_filename.relative_path())
pickle_path = filename + '.p'
return os.path.join(output_dir, pickle_path)


def _PickleAST(ast, output_file):
full_dir = os.path.dirname(output_file)
fileutil.EnsureDirectoryExists(full_dir)

try:
with open(output_file, "wb") as f:
cPickle.dump(ast, f)
except (IOError, cPickle.PicklingError) as e:
print "%s: Error: %s" % (output_file, str(e))
sys.exit(1)


def _ParseFile(args, rel_filename):
try:
with open(rel_filename.path) as f:
source = f.read()
except IOError as e:
print "%s: Error: %s" % (rel_filename.path, e.strerror)
sys.exit(1)

try:
tree = Parse(source, rel_filename.path)
RemoveDisabledDefinitions(tree, args.enabled_features)
except Error as e:
print "%s: Error: %s" % (rel_filename.path, str(e))
sys.exit(1)

_PickleAST(tree, _GetPicklePath(rel_filename, args.output_dir))


def _Parse(args, _):
fileutil.EnsureDirectoryExists(args.output_dir)
for filename in args.filename:
_ParseFile(args, RelativePath(filename, args.depth))
return 0


def _Precompile(args, _):
generator_modules = LoadGenerators(",".join(_BUILTIN_GENERATORS.keys()))

Expand All @@ -305,6 +314,29 @@ def main():
help="use Python modules bundled in the SDK")

subparsers = parser.add_subparsers()

parse_parser = subparsers.add_parser(
"parse", description="Parse mojom to AST and remove disabled definitions."
" Pickle pruned AST into output_dir.")
parse_parser.add_argument("filename", nargs="+", help="mojom input file")
parse_parser.add_argument(
"-o",
"--output_dir",
dest="output_dir",
default=".",
help="output directory for generated files")
parse_parser.add_argument(
"-d", "--depth", dest="depth", default=".", help="depth from source root")
parse_parser.add_argument(
"--enable_feature",
dest = "enabled_features",
default=[],
action="append",
help="Controls which definitions guarded by an EnabledIf attribute "
"will be enabled. If an EnabledIf attribute does not specify a value "
"that matches one of the enabled features, it will be disabled.")
parse_parser.set_defaults(func=_Parse)

generate_parser = subparsers.add_parser(
"generate", description="Generate bindings from mojom files.")
generate_parser.add_argument("filename", nargs="+",
Expand Down Expand Up @@ -359,12 +391,6 @@ def main():
generate_parser.add_argument(
"--generate_non_variant_code", action="store_true",
help="Generate code that is shared by different variants.")
generate_parser.add_argument(
"--depfile",
help="A file into which the list of input files will be written.")
generate_parser.add_argument(
"--depfile_target",
help="The target name to use in the depfile.")
generate_parser.add_argument(
"--scrambled_message_id_salt_path",
dest="scrambled_message_id_salt_paths",
Expand Down
Loading

0 comments on commit f83c71b

Please sign in to comment.