blob: 67c57af88b5046eff6dd8d0655575a067e1566bb [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Compare the outputs of GN and ZN toolchains when building the same canary
targets. In case of difference, an error message will explain the issue, and
the content of OUTPUT_DIR can be manually reviewed by a human to look at
differences.
This scripts must be run from the top-level Fuchsia directory, it will work
as follows:
1) Create two directories (out/toolchains.gn and out/toolchains.zn by default)
and create an args.gn in each one of them, to be parse by the Fuchsia and
Zircon build respectively, directing the graph to the canary targets.
2) Invoke 'gn gen' on both directories to invoke the Fuchsia and Zircon
builds, respectively.
3) For a specific set of GN/ZN toolchain pairs, compare the content of
their respective toolchain.ninja file to verify that they use the same
tool() definitions.
4) For each canary target, Invoke 'ninja -C <dir> -t commands <target>' to
get the list of commands used to build the target, process the result
slightly to account for differences between the two build systems,
then compare the results.
5) Populate OUTPUT_DIR/{gn,zn}/<toolchain>/ directories with `rules.json`
file that corresponds to the toolchain.ninja file for each toolchain,
as well as `<target>.commands` containing a prettified list of commands
for each target.
This makes it easy to review differences manually, especially with a
graphical tool, e.g. "meld OUTPUT_DIR/zn OUTPUT_DIR/gn" will give a
very useful overview of the differences.
"""
import argparse
import difflib
import json
import os
import platform
import shutil
import subprocess
import sys
# The prefix of the two directories where ninja build files will be generated,
# related to $FUCHSIA_DIR/out/. This script will populate
# $FUCSHIA_DIR/out/$PREFIX.zn and $FUCHSIA_DIR/out/$PREFIX.gn and compare
# their content.
_DEFAULT_OUT_PREFIX = 'toolchains'
# The list of toolchains to compare. For now, this is a list of scopes
# with the following schema:
#
# name: Name for this toolchain pair / comparison. Will be used
# to create an $OUTPUT_DIR/{gn,zn}/$NAME directory where processed
# outputs will be stored for human review.
#
# variants: [optional] List of variants to apply.
#
# gn.toolchain: GN-build toolchain label.
# zn.toolchain: ZN-build toolchain label.
#
# output_extension: [optional] Executable extension for this toolchain,
# default is empty (no extension).
#
# no_shared: [optional] Boolean set to True if these toolchains do not
# support building shared libraries. Default is False.
#
_ALL_TOOLCHAINS = [
{
'name': 'user.vdso_x64',
'gn': {
'toolchain': '//zircon/system/ulib/zircon:user.vdso_x64',
},
'zn': {
'toolchain': '//system/ulib/zircon:user.vdso-x64-clang',
},
},
{
'name': 'user.vdso_x64-gcc',
'variants': ['gcc'],
'gn': {
'toolchain': '//zircon/system/ulib/zircon:user.vdso_x64-gcc',
},
'zn': {
'toolchain': '//system/ulib/zircon:user.vdso-x64-gcc',
},
},
{
'name': 'user.vdso_arm64',
'gn': {
'toolchain': '//zircon/system/ulib/zircon:user.vdso_arm64',
},
'zn': {
'toolchain': '//system/ulib/zircon:user.vdso-arm64-clang',
},
},
{
'name': 'user.vdso_arm64-gcc',
'variants': ['gcc'],
'gn': {
'toolchain': '//zircon/system/ulib/zircon:user.vdso_arm64-gcc',
},
'zn': {
'toolchain': '//system/ulib/zircon:user.vdso-arm64-gcc',
},
},
{
'name': 'physmem_arm64',
'gn': {
'toolchain': '//zircon/kernel/arch/arm64:physmem_arm64',
},
'zn': {
'toolchain': '//kernel/arch/arm64:physmem-arm64-clang',
},
'no_shared': True,
},
{
'name': 'physmem_arm64-gcc',
'variants': ['gcc'],
'gn': {
'toolchain': '//zircon/kernel/arch/arm64:physmem_arm64-gcc',
},
'zn': {
'toolchain': '//kernel/arch/arm64:physmem-arm64-gcc',
},
'no_shared': True,
},
{
'name': 'kernel.phys32',
'gn': {
'toolchain': '//zircon/kernel/arch/x86/phys:kernel.phys32',
},
'zn': {
'toolchain': '//kernel/arch/x86/phys:kernel.phys32-x64-clang',
},
'no_shared': True,
},
{
'name': 'kernel.phys32-gcc',
'variants': ['gcc'],
'gn': {
'toolchain': '//zircon/kernel/arch/x86/phys:kernel.phys32-gcc',
},
'zn': {
'toolchain': '//kernel/arch/x86/phys:kernel.phys32-x64-gcc',
},
'no_shared': True,
},
{
'name': 'kernel.phys-x64',
'gn': {
'toolchain': '//zircon/kernel/phys:kernel.phys_x64',
},
'zn': {
'toolchain': '//kernel/phys:kernel.phys-x64-clang',
},
'no_shared': True,
},
{
'name': 'kernel.phys-arm64',
'gn': {
'toolchain': '//zircon/kernel/phys:kernel.phys_arm64',
},
'zn': {
'toolchain': '//kernel/phys:kernel.phys-arm64-clang',
},
'no_shared': True,
},
{
'name': 'kernel.phys-x64-gcc',
'variants': ['gcc'],
'gn': {
'toolchain': '//zircon/kernel/phys:kernel.phys_x64-gcc',
},
'zn': {
'toolchain': '//kernel/phys:kernel.phys-x64-gcc',
},
'no_shared': True,
},
{
'name': 'kernel.phys-arm64-gcc',
'variants': ['gcc'],
'gn': {
'toolchain': '//zircon/kernel/phys:kernel.phys_arm64-gcc',
},
'zn': {
'toolchain': '//kernel/phys:kernel.phys-arm64-gcc',
},
'no_shared': True,
},
{
'name': 'userboot_arm64',
'gn':
{
'toolchain':
'//zircon/kernel/lib/userabi/userboot:userboot_arm64',
},
'zn':
{
'toolchain':
'//kernel/lib/userabi/userboot:userboot-arm64-clang',
},
'no_shared': True,
},
{
'name': 'userboot_x64',
'gn':
{
'toolchain':
'//zircon/kernel/lib/userabi/userboot:userboot_x64',
},
'zn': {
'toolchain': '//kernel/lib/userabi/userboot:userboot-x64-clang',
},
'no_shared': True,
},
{
'name': 'userboot_arm64-gcc',
'variants': ['gcc'],
'gn':
{
'toolchain':
'//zircon/kernel/lib/userabi/userboot:userboot_arm64-gcc',
},
'zn': {
'toolchain': '//kernel/lib/userabi/userboot:userboot-arm64-gcc',
},
'no_shared': True,
},
{
'name': 'userboot_x64-gcc',
'variants': ['gcc'],
'gn':
{
'toolchain':
'//zircon/kernel/lib/userabi/userboot:userboot_x64-gcc',
},
'zn': {
'toolchain': '//kernel/lib/userabi/userboot:userboot-x64-gcc',
},
'no_shared': True,
},
]
_TOOLCHAIN_NAMES = [e['name'] for e in _ALL_TOOLCHAINS]
# The list of GN tool names that needs to be compared. The others are ignored
# by this script (e.g. Objective-C, Rust and Copy + Stamp).
_COMMON_TOOLS = {'alink', 'link', 'asm', 'cc', 'cxx', 'solink', 'solink_module'}
# The Clang binary directory, as it should appear in build commands.
_CLANG_BINPREFIX = '../../prebuilt/third_party/clang/linux-x64/bin'
def _entry_to_variant_name(e):
"""Return the variant-specific name for entry |e| in _ALL_TOOLCHAINS."""
return '-'.join(e.get('variants', []))
def _recreate_directory(dir_path):
"""Create or cleanup |dir_path| directory path."""
if os.path.isdir(dir_path):
shutil.rmtree(dir_path)
os.makedirs(dir_path)
def _write_file(path, data):
"""Write |data| to file |path|."""
with open(path, 'w') as f:
f.write(data)
def _cmd_output(cmds):
"""Return shell command output as a string."""
return subprocess.check_output(cmds).decode('utf-8').rstrip()
def _write_dict_as_json(json_file, rules):
"""Write dictionary as a JSON file."""
with open(json_file, 'w') as f:
json.dump(rules, f, indent=2, sort_keys=True)
def _generate_gn_args_for_gn_build(toolchains, variant_name):
"""Generate the args.gn file used by the GN canary build."""
result = '# Auto-generated - DO NOT EDIT\n'
result += 'base_package_labels = []\n'
result += 'cache_package_labels = []\n'
result += 'universe_package_labels = [\n'
for t in toolchains:
if variant_name == _entry_to_variant_name(t):
result += ' "//zircon/public/canaries:canaries(%s)",' % t['gn'][
'toolchain']
result += ']\n'
if variant_name:
variants = variant_name.split('-')
result += 'select_variant = [ %s ]\n' % ', '.join(
'"%s"' % v for v in variants)
return result
def _generate_gn_args_for_zn_build(toolchains, variant_name):
"""Generate the args.gn file used by the ZN canary build."""
result = '# Auto-generated - DO NOT EDIT\n'
result += "default_deps = [\n"
for t in toolchains:
if variant_name == _entry_to_variant_name(t):
result += ' "//public/canaries:canaries(%s)",' % t['zn'][
'toolchain']
result += "]\n"
if variant_name:
variants = variant_name.split('-')
result += 'variants = [ %s ]\n' % ', '.join(
'"%s"' % v for v in variants)
return result
def parse_toolchain_ninja_file(toolchain_ninja, toolchain):
"""Parse the content of a toolchain.ninja file.
Parse the content of a toolchain.ninja file, and extract its tool-specific
entries (e.g. ${toolchain}_alink, ${toolchain}_link, etc).
Ignoring the rest.
Args:
toolchain_ninja: A string holding the toolchain.ninja file content.
toolchain: The toolchain's name, will be replaced by 'TOOLCHAIN'
in the output.
Returns:
A { tool_name -> tool_dict } dictionary, where |tool_name| is a GN
tool() name, and tool_dict() is a dictionary of the corresponding
entries from the input data, with "${toolchain}" replaced with
'TOOLCHAIN'. E.g.:
{
"alink": {
"command": "......",
"description": "....",
"rspfile": "....",
...
},
...
}
"""
content = toolchain_ninja.replace(toolchain, 'TOOLCHAIN')
tool_name = None
tool_rule_prefix = 'rule TOOLCHAIN_'
result = {}
for line in content.splitlines():
line = line.rstrip()
if not line:
# Empty lines exit the tool section.
tool_name = None
continue
if line[0] != ' ':
# Expected format for the start of a tool section:
# rule ${toolchain}_${tool}.
if line.startswith(tool_rule_prefix):
tool_name = line[len(tool_rule_prefix):]
result[tool_name] = {}
continue
elif tool_name:
# Expected format for a tool section line:
# <key> = <value>
key, separator, value = line.partition('=')
assert separator == '=', 'Unknown tool section line: ' + line
key = key.strip()
value = value.strip()
result[tool_name][key] = value
continue
# Something else, ignore and exit tool section if any.
tool_name = None
return result
def _remove_unnecessary_tools(rules):
"""Remove any un-needed tools from a set of toolchain rules.
Args:
rules: A { tool name -> tool dict } dictionary as returned by
parse_toolchain_ninja_file().
Returns:
The input dictionary, but only with the keys from _COMMON_TOOLS.
"""
return {k: v for k, v in rules.items() if k in _COMMON_TOOLS}
def _load_toolchain_ninja(out_dir, toolchain_label):
"""Load a toolchain.ninja path for a given toolchain.
Args:
out_dir: Root output directory (e.g. "$FUCHSIA_DIR/out/toolchains.gn")
toolchain: Toolchain GN label (e.g. "//build/toolchain:host_x64").
Returns:
A (content, toolchain) tuple, where |content| is a string containing
the file's content, and |toolchain| is the toolchain's name
(e.g. "host_x64").
"""
_, _, toolchain_name = toolchain_label.partition(':')
toolchain_ninja_path = os.path.join(
out_dir, toolchain_name, 'toolchain.ninja')
with open(toolchain_ninja_path, 'r') as f:
toolchain_ninja = f.read()
return (toolchain_ninja, toolchain_name)
def pretty_print_commands_list(commands):
"""Convert a list of commands into a pretty-printed string."""
result = ""
for cmd in commands:
cmd0, _, _ = cmd.partition(' ')
is_compile_or_link = (
cmd0.endswith('clang') or cmd0.endswith('clang++') or
cmd0.endswith('g++') or cmd0.endswith('gcc'))
if is_compile_or_link:
# This is a compiler/linker command, use one argument per line.
cmd = ' \\\n '.join(cmd.split(' '))
else:
# This is a regular command, split at && instead.
cmd = '&& \\\n '.join(cmd.split('&&'))
result += cmd + '\n'
return result
def _write_commands_to_file(path, commands):
"""Write target commands to a file, pretty-printing it to help comparison."""
with open(path, 'w') as f:
f.write(pretty_print_commands_list(commands))
def _remove_gn_config_deps_touch_commands(commands):
"""Remove extra stamp touch commands from GN build.
The Fuchsia build adds stamps for config_deps groups, remove
them since they are not important for this comparison.
They look like:
touch TOOLCHAIN/obj/.../${config}_deps.stamp
Args:
commands: List of command strings from the GN build.
Returns:
A new list of command strings.
"""
return [
cmd for cmd in commands if not (
cmd.startswith('touch TOOLCHAIN/') and cmd.endswith('deps.stamp'))
]
def _update_gn_executable_output_directory(commands):
"""Update the output path of executables and response files.
The GN and ZN builds place their executables in different locations
so adjust then GN ones to match the ZN ones.
Args:
commands: list of command strings from the GN build.
Returns:
A new list of command strings.
"""
replacements = {
'TOOLCHAIN/main_with_static':
'TOOLCHAIN/obj/public/canaries/main_with_static',
'TOOLCHAIN/main_with_shared':
'TOOLCHAIN/obj/public/canaries/main_with_shared',
'TOOLCHAIN_SHARED/libfoo_shared':
'TOOLCHAIN_SHARED/obj/public/canaries/libfoo_shared',
}
result = []
for cmd in commands:
for key, val in replacements.items():
cmd = cmd.replace(key, val)
result.append(cmd)
return result
def _update_root_build_dir(commands, root_build_dir):
"""Update the GN root build dir when it appears in commands.
The absolute root build dir sometimes appears in build commands (e.g.
when building with a 'gcc' variant, in a -fdebug-prefix=<...> compiler
argument). This function replaces it with the hard-coded ROOT_BUILD_DIR
string.
Args:
commands: list of command strings from either build.
root_build_dir: root build dir path.
Returns:
A new list of command strings.
"""
root_build_dir = os.path.abspath(root_build_dir)
return [cmd.replace(root_build_dir, 'ROOT_BUILD_DIR') for cmd in commands]
def _update_zn_prebuilt_path(commands, root_dir):
"""Update the absolute prebuilt directory when it appears in ZN commands.
The absolute root prebuilt dir sometimes appears in ZN build commands, e.g.
when using a 'gcc' variant, it will appear in a compiler argument
that lists the path to the libc++ library. Meanwhile in the GN build, the
corresponding relative path (i.e. '../../prebuilt') will be used instead.
The reason for this is that GN will change the value of 'lib_dirs' from
absolute to relative when writing Ninja toolchain definitions, if the path
is absolute *and* in-tree.
In the case of the GN build, prebuilt is under the root directory, so the
substitution always happen (and cannot be disabled). In the case of the
ZN build, whose root_dir is really FUCHSIA_DIR/zircon/, the prebuilt
directory is out-of-tree so the value written to toolchain.ninja will still
be absolute for the same compiler argument.
This function deals with the issue by replacing the absolute prebuilt
directory path with '../../prebuilt' string, to match the GN build.
"""
prebuilt_dir = os.path.abspath(os.path.join(root_dir, 'prebuilt'))
return [cmd.replace(prebuilt_dir, '../../prebuilt') for cmd in commands]
def _check_toolchain_rules(
gn_rules, gn_toolchain, zn_rules, zn_toolchain, verbose):
"""Check the toolchain rules between a GN toolchain and its ZN equivalent.
Args:
gn_rules: A { tool -> { key: value } } map, as returned by
parse_toolchain_ninja_file(), corresponding to a GN toolchain.
gn_toolchain: The GN toolchain label.
zn_rules: Same as |gn_rules|, for the corresponding ZN toolchain.
zn_toolchain: The ZN toolchain label.
verbose: If True, add key value differences to error messages.
Returns:
A (warnings, errors) pair, where |warnings| and |errors| are list of
strings describing warnings or errors found in the comparison.
"""
warnings = []
errors = []
# Check that all tools in the ZN toolchain are in the GN one.
zn_tools = set(zn_rules.keys())
gn_tools = set(gn_rules.keys())
missing_tools = zn_tools - gn_tools
if missing_tools:
message = 'Missing tools from GN:%s (compared with ZN:%s): %s' % (
gn_toolchain, zn_toolchain, ' '.join(sorted(missing_tools)))
errors += [message]
# For each tool, check that the keys are the same.
# Then check that their values are also identical.
for tool in zn_tools:
zn_tool = zn_rules[tool]
gn_tool = gn_rules[tool]
gn_keys = set(gn_tool.keys())
zn_keys = set(zn_tool.keys())
extra_keys = gn_keys - zn_keys
if extra_keys:
warnings += [
'GN:%s: extra %s tool keys: %s' %
(gn_toolchain, tool, sorted(extra_keys))
]
missing_keys = zn_keys - gn_keys
if missing_keys:
errors += [
'GN:%s: Missing %s tool keys: %s' %
(gn_toolchain, tool, sorted(missing_keys))
]
for key in gn_keys & zn_keys:
zn_value = zn_tool[key]
gn_value = gn_tool[key]
if zn_value != gn_value:
message = 'GN:%s:%s: %s key values differ' % (
gn_toolchain, tool, key)
if verbose:
message += '\n gn [%s]\n zn [%s]\n' % (gn_value, zn_value)
errors += [message]
return warnings, errors
def main():
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'output_dir',
metavar='OUTPUT_DIR',
help=(
'Output directory where processed toolchain rules and commands ' +
'will be written for human review and comparison. Must not exist ' +
'unless --clean is used.'))
parser.add_argument(
'--clean',
action='store_true',
help=(
'Cleanup output directory if it already exists, instead of ' +
'aborting.'))
parser.add_argument(
'--verbose',
action='store_true',
help='Print differences with error messages.')
parser.add_argument(
'--toolchain',
help=
'Comma-separated list of toolchains to check. Default is all of them')
parser.add_argument(
'--root-dir',
help='Root Fuchsia source directory. Default is auto-detected.')
parser.add_argument(
'--gn',
help='Path to GN executable. The prebuilt Fuchsia one will be used by '
'default.')
parser.add_argument(
'--ninja',
help='Path to Ninja executable. The prebuilt Fuchsia one will be used '
'by default.')
parser.add_argument(
'--out-prefix',
default=_DEFAULT_OUT_PREFIX,
help='Prefix of output directories used for comparison, relative to ' +
'$ROOT_DIR/out/. Default is [%s]' % _DEFAULT_OUT_PREFIX)
parser.add_argument(
'--skip-gen',
action='store_true',
help='Skip \'gn gen\' step. Only used for developing this script.')
args = parser.parse_args()
if args.root_dir is None:
# Assume this script os under //zircon/public/canaries/
root_dir = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..'))
else:
root_dir = args.root_dir
assert os.path.isdir(root_dir), 'Missing root directory: ' + root_dir
root_dir = os.path.abspath(root_dir)
# Create or cleanup output directory if needed.
output_dir = os.path.abspath(args.output_dir)
if os.path.isdir(output_dir):
if not args.clean:
print(
'ERROR: Output directory already exists. Consider using ' +
'--clean to remove its content and use it.',
file=sys.stderr)
return 1
_recreate_directory(output_dir)
else:
os.makedirs(output_dir)
host_cpu = platform.machine()
host_cpu = {
'x86_64': 'x64',
'aarch64': 'arm64',
}.get(host_cpu, host_cpu)
host_os = platform.system()
host_os = {
'Linux': 'linux',
'Darwin': 'mac',
'Windows': 'win',
}.get(host_os, host_os)
host_name = '%s-%s' % (host_os, host_cpu)
if args.gn:
gn_path = args.gn
else:
gn_path = os.path.join(
root_dir, 'prebuilt', 'third_party', 'gn', host_name, 'gn')
if args.ninja:
ninja_path = args.ninja
else:
ninja_path = os.path.join(
root_dir, 'prebuilt', 'third_party', 'ninja', host_name, 'ninja')
all_toolchains = _ALL_TOOLCHAINS
if args.toolchain:
toolchain_names = set(args.toolchain.split(','))
for name in toolchain_names:
if name not in _TOOLCHAIN_NAMES:
parser.error(
'Invalid toolchain name %s, should be one of:\n%s\n' %
(name, '\n'.join(' %s' % t for t in _TOOLCHAIN_NAMES)))
all_toolchains = [
t for t in _ALL_TOOLCHAINS if t['name'] in toolchain_names
]
all_variant_names = sorted(
{_entry_to_variant_name(e) for e in all_toolchains})
# Generate all GN and ZN output directories, based on the variants being used.
gn_out_dirs = {}
zn_out_dirs = {}
for variant in all_variant_names:
suffix = '-' + variant if variant else ''
gn_out_dir = os.path.join(
root_dir, 'out', args.out_prefix + suffix + '.gn')
zn_out_dir = os.path.join(
root_dir, 'out', args.out_prefix + suffix + '.zn')
gn_out_dirs[variant] = gn_out_dir
zn_out_dirs[variant] = zn_out_dir
if not args.skip_gen:
# Generate the $ROOT_DIR/out/$PREFIX.gn/args.gn
_recreate_directory(gn_out_dir)
_write_file(
os.path.join(gn_out_dir, 'args.gn'),
_generate_gn_args_for_gn_build(all_toolchains, variant))
# Populate $ROOT_DIR/out/$PREFIX.gn now.
subprocess.check_call(
[gn_path, 'gen',
os.path.relpath(gn_out_dir, start=root_dir)],
cwd=root_dir)
# Generate $ROOT_DIR/out/$PREFIX.zn/args.gn
_recreate_directory(zn_out_dir)
_write_file(
os.path.join(zn_out_dir, 'args.gn'),
_generate_gn_args_for_zn_build(all_toolchains, variant))
# Populate $ROOT_DIR/out/$PREFIX.zn now
subprocess.check_call(
[
gn_path, 'gen', '--root=zircon',
os.path.relpath(zn_out_dir, start=root_dir)
],
cwd=root_dir)
for t in all_toolchains:
gn_toolchain = t['gn']['toolchain']
zn_toolchain = t['zn']['toolchain']
variant = _entry_to_variant_name(t)
variant_suffix = '-' + variant if variant else ''
gn_out_dir = gn_out_dirs[variant]
zn_out_dir = zn_out_dirs[variant]
gn_toolchain_ninja, gn_toolchain_name = _load_toolchain_ninja(
gn_out_dir, gn_toolchain)
gn_toolchain_rules = parse_toolchain_ninja_file(
gn_toolchain_ninja, gn_toolchain_name)
zn_toolchain_ninja, zn_toolchain_name = _load_toolchain_ninja(
zn_out_dir, zn_toolchain)
zn_toolchain_rules = parse_toolchain_ninja_file(
zn_toolchain_ninja, zn_toolchain_name)
gn_toolchain_rules = _remove_unnecessary_tools(gn_toolchain_rules)
zn_toolchain_rules = _remove_unnecessary_tools(zn_toolchain_rules)
# All outputs for this toolchain pair will be in this directory.
gn_output_dir = os.path.join(output_dir, 'gn', t['name'])
zn_output_dir = os.path.join(output_dir, 'zn', t['name'])
os.makedirs(gn_output_dir)
os.makedirs(zn_output_dir)
_write_dict_as_json(
os.path.join(gn_output_dir, 'rules.json'), gn_toolchain_rules)
_write_dict_as_json(
os.path.join(zn_output_dir, 'rules.json'), zn_toolchain_rules)
warnings, errors = _check_toolchain_rules(
gn_toolchain_rules, gn_toolchain, zn_toolchain_rules, zn_toolchain,
args.verbose)
for w in warnings:
print('WARNING: %s' % w, file=sys.stderr)
# Compare output commands for canary targets now.
targets = ['main_with_static']
if not t.get('no_shared', False):
targets += ['main_with_shared']
extension = ""
if 'output_extension' in t:
extension = "." + t['output_extension']
for target in targets:
# The output directories are derived from the toolchain name.
gn_outdir = gn_toolchain_name
zn_outdir = zn_toolchain_name + '/obj/public/canaries'
gn_outfile = os.path.join(gn_outdir, target) + extension
zn_outfile = os.path.join(zn_outdir, target) + extension
print(
'%s: Comparing commands for GN [%s] and ZN [%s]' %
(t['name'], gn_outfile, zn_outfile))
gn_commands = _cmd_output(
[ninja_path, '-C', gn_out_dir, '-t', 'commands', gn_outfile])
zn_commands = _cmd_output(
[ninja_path, '-C', zn_out_dir, '-t', 'commands', zn_outfile])
# Replace toolchain name with TOOLCHAIN
gn_commands = gn_commands.replace(
gn_toolchain_name + '/', 'TOOLCHAIN/')
zn_commands = zn_commands.replace(
zn_toolchain_name + '/', 'TOOLCHAIN/')
is_shared = target.endswith('shared')
if is_shared:
gn_commands = gn_commands.replace(
gn_toolchain_name + '-shared/', 'TOOLCHAIN_SHARED/')
zn_commands = zn_commands.replace(
zn_toolchain_name + '.shlib/', 'TOOLCHAIN_SHARED/')
# Build configurations come from //public/gn/config in the ZN build
# and from //build/config/zircon in the GN one. These path can
# appear in commands in certain cases.
gn_commands = gn_commands.replace(
'build/config/zircon/', 'BUILD_CONFIGS/')
zn_commands = zn_commands.replace(
'zircon/public/gn/config/', 'BUILD_CONFIGS/')
# Remove /zircon/ sub-path from GN build commands.
gn_commands = gn_commands.replace('/obj/zircon/', '/obj/').replace(
'/gen/zircon/', '/gen/')
# Replace out/toolchain.{gn,zn} with BUILD_ROOT_DIR
gn_commands = gn_commands.replace(
'out/%s.gn' % args.out_prefix, 'BUILD_ROOT_DIR')
zn_commands = zn_commands.replace(
'out/%s.zn' % args.out_prefix, 'BUILD_ROOT_DIR')
# Split lines.
gn_commands = gn_commands.splitlines()
zn_commands = zn_commands.splitlines()
# Sanitize GN commands a little.
gn_commands = _remove_gn_config_deps_touch_commands(gn_commands)
gn_commands = _update_gn_executable_output_directory(gn_commands)
gn_commands = _update_root_build_dir(gn_commands, gn_out_dir)
zn_commands = _update_root_build_dir(zn_commands, zn_out_dir)
zn_commands = _update_zn_prebuilt_path(zn_commands, root_dir)
if len(gn_commands) != len(zn_commands):
errors += [
'%s: GN(%s) vs ZN(%s) line count mismatch: %d vs %d' % (
target, gn_toolchain_name, zn_toolchain_name,
len(gn_commands), len(zn_commands))
]
# Compare commands list.
differences = list(
difflib.unified_diff(gn_commands, zn_commands, n=2))
if len(differences) > 0:
message = (
'%s: GN(%s) vs ZN(%s) have different content!' %
(target, gn_toolchain_name, zn_toolchain_name))
if args.verbose:
message += '\n %s' % '\n '.join(differences)
errors += [message]
_write_commands_to_file(
os.path.join(gn_output_dir, '%s.commands' % target),
gn_commands)
_write_commands_to_file(
os.path.join(zn_output_dir, '%s.commands' % target),
zn_commands)
if errors:
for e in errors:
print('ERROR: %s' % e, file=sys.stderr)
print('For full details, see the content of: %s' % output_dir)
return 1
print('OK.')
return 0
if __name__ == "__main__":
sys.exit(main())