BUILD.gn: Remove use of exec_script

Chromium's BUILD files try to avoid uses of exec_script when possible
because they slow down every GN invocation. In preparation for building
Dawn inside Chromium, the calls to exec_script for the code generator
are removed.

In GN, the generator now outputs a "JSON tarball", a dictionnary mapping
filenames to content. This allows us to use the "depfile" feature of GN
to avoid the exec_script call to gather the script's inputs.

Outputs of the generator are now listed in the BUILD.gn files. To keep
it in sync with the generator, GN outputs a file containing "expected
outputs" that is checked by the code generator.

Finally the dawn_generator GN template doesn't create a target anymore,
but users are expected to gather outputs using get_target_outputs.
This commit is contained in:
Corentin Wallez
2018-08-16 15:32:35 +02:00
committed by Corentin Wallez
parent d273d33a63
commit 59e7fad99b
3 changed files with 276 additions and 163 deletions

View File

@@ -280,21 +280,20 @@ class PreprocessingLoader(jinja2.BaseLoader):
FileRender = namedtuple('FileRender', ['template', 'output', 'params_dicts'])
def do_renders(renders, template_dir, output_dir):
FileOutput = namedtuple('FileOutput', ['name', 'content'])
def do_renders(renders, template_dir):
env = jinja2.Environment(loader=PreprocessingLoader(template_dir), trim_blocks=True, line_comment_prefix='//*')
outputs = []
for render in renders:
params = {}
for param_dict in render.params_dicts:
params.update(param_dict)
output = env.get_template(render.template).render(**params)
content = env.get_template(render.template).render(**params)
outputs.append(FileOutput(render.output, content))
output_file = output_dir + os.path.sep + render.output
directory = os.path.dirname(output_file)
if not os.path.exists(directory):
os.makedirs(directory)
with open(output_file, 'w') as outfile:
outfile.write(output)
return outputs
#############################################################
# MAIN SOMETHING WHATEVER
@@ -389,37 +388,7 @@ def js_native_methods(types, typ):
def debug(text):
print(text)
def main():
targets = ['dawn_headers', 'libdawn', 'mock_dawn', 'dawn_wire', "dawn_native_utils"]
parser = argparse.ArgumentParser(
description = 'Generates code for various target for Dawn.',
formatter_class = argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('json', metavar='DAWN_JSON', nargs=1, type=str, help ='The DAWN JSON definition to use.')
parser.add_argument('-t', '--template-dir', default='templates', type=str, help='Directory with template files.')
parser.add_argument('-o', '--output-dir', default=None, type=str, help='Output directory for the generated source files.')
parser.add_argument('-T', '--targets', default=None, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(targets))
parser.add_argument(kExtraPythonPath, default=None, type=str, help='Additional python path to set before loading Jinja2')
parser.add_argument('--print-dependencies', action='store_true', help='Prints a space separated list of file dependencies, used for CMake integration')
parser.add_argument('--print-outputs', action='store_true', help='Prints a space separated list of file outputs, used for CMake integration')
parser.add_argument('--gn', action='store_true', help='Make the printing of dependencies/outputs GN-friendly')
args = parser.parse_args()
if args.targets != None:
targets = args.targets.split(',')
with open(args.json[0]) as f:
loaded_json = json.loads(f.read())
# A fake api_params to avoid parsing the JSON when just querying dependencies and outputs
api_params = {
'types': {}
}
if not args.print_outputs and not args.print_dependencies:
api_params = parse_json(loaded_json)
def get_renders_for_targets(api_params, targets):
base_params = {
'enumerate': enumerate,
'format': format,
@@ -491,7 +460,61 @@ def main():
renders.append(FileRender('dawn_wire/WireClient.cpp', 'dawn_wire/WireClient.cpp', wire_params))
renders.append(FileRender('dawn_wire/WireServer.cpp', 'dawn_wire/WireServer.cpp', wire_params))
output_separator = '\n' if args.gn else ';'
return renders
def output_to_files(outputs, output_dir):
for output in outputs:
output_file = output_dir + os.path.sep + output.name
directory = os.path.dirname(output_file)
if not os.path.exists(directory):
os.makedirs(directory)
with open(output_file, 'w') as outfile:
outfile.write(output.content)
def output_to_json(outputs, output_json):
json_root = {}
for output in outputs:
json_root[output.name] = output.content
with open(output_json, 'w') as f:
f.write(json.dumps(json_root))
def output_depfile(depfile, output, dependencies):
with open(depfile, 'w') as f:
f.write(output + ": " + " ".join(dependencies))
def main():
allowed_targets = ['dawn_headers', 'libdawn', 'mock_dawn', 'dawn_wire', "dawn_native_utils"]
parser = argparse.ArgumentParser(
description = 'Generates code for various target for Dawn.',
formatter_class = argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('json', metavar='DAWN_JSON', nargs=1, type=str, help ='The DAWN JSON definition to use.')
parser.add_argument('-t', '--template-dir', default='templates', type=str, help='Directory with template files.')
parser.add_argument('-T', '--targets', required=True, type=str, help='Comma-separated subset of targets to output. Available targets: ' + ', '.join(allowed_targets))
# Arguments used only for the GN build
parser.add_argument(kExtraPythonPath, default=None, type=str, help='Additional python path to set before loading Jinja2')
parser.add_argument('--output-json-tarball', default=None, type=str, help='Name of the "JSON tarball" to create (tar is too annoying to use in python).')
parser.add_argument('--depfile', default=None, type=str, help='Name of the Ninja depfile to create for the JSON tarball')
parser.add_argument('--expected-outputs-file', default=None, type=str, help="File to compare outputs with and fail if it doesn't match")
# Arguments used only for the CMake build
parser.add_argument('-o', '--output-dir', default=None, type=str, help='Output directory for the generated source files.')
parser.add_argument('--print-dependencies', action='store_true', help='Prints a space separated list of file dependencies, used for CMake integration')
parser.add_argument('--print-outputs', action='store_true', help='Prints a space separated list of file outputs, used for CMake integration')
args = parser.parse_args()
# Load and parse the API json file
with open(args.json[0]) as f:
loaded_json = json.loads(f.read())
api_params = parse_json(loaded_json)
targets = args.targets.split(',')
renders = get_renders_for_targets(api_params, targets)
# Print outputs and dependencies for CMake
if args.print_dependencies:
dependencies = set(
[os.path.abspath(args.template_dir + os.path.sep + render.template) for render in renders] +
@@ -499,7 +522,7 @@ def main():
[os.path.realpath(__file__)]
)
dependencies = [dependency.replace('\\', '/') for dependency in dependencies]
sys.stdout.write(output_separator.join(dependencies))
sys.stdout.write(';'.join(dependencies))
return 0
if args.print_outputs:
@@ -507,10 +530,36 @@ def main():
[os.path.abspath(args.output_dir + os.path.sep + render.output) for render in renders]
)
outputs = [output.replace('\\', '/') for output in outputs]
sys.stdout.write(output_separator.join(outputs))
sys.stdout.write(';'.join(outputs))
return 0
do_renders(renders, args.template_dir, args.output_dir)
# The caller wants to assert that the outputs are what it expects.
# Load the file and compare with our renders. GN-only.
if args.expected_outputs_file != None:
with open(args.expected_outputs_file) as f:
expected = set([line.strip() for line in f.readlines()])
actual = set()
actual.update([render.output for render in renders])
if actual != expected:
print("Wrong expected outputs, caller expected:\n " + repr(list(expected)))
print("Actual output:\n " + repr(list(actual)))
return 1
outputs = do_renders(renders, args.template_dir)
# CMake only: output all the files directly.
if args.output_dir != None:
output_to_files(outputs, args.output_dir)
# GN only: output the tarball and its depfile
if args.output_json_tarball != None:
output_to_json(outputs, args.output_json_tarball)
dependencies = [args.template_dir + os.path.sep + render.template for render in renders]
dependencies.append(args.json[0])
output_depfile(args.depfile, args.output_json_tarball, dependencies)
if __name__ == '__main__':
sys.exit(main())