0

Gut gyp_chromium, common.gypi.

This change removes all of the code referencing gyp_chromium that
isn't needed to keep the closure_compilation builder working and/or
also needed for proper functioning of the gclient hooks.

R=brettw@chromium.org, dbeam@chromium.org
BUG=570091, 632206
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2355673003
Cr-Commit-Position: refs/heads/master@{#420182}
This commit is contained in:
dpranke
2016-09-21 15:27:23 -07:00
committed by Commit bot
parent d7f480d509
commit 0730244c01
8 changed files with 16 additions and 7266 deletions

@ -1,16 +0,0 @@
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
def _RunTests(input_api, output_api):
return (input_api.canned_checks.RunUnitTestsInDirectory(
input_api, output_api, '.', whitelist=[r'.+_test.py$']))
def CheckChangeOnUpload(input_api, output_api):
return _RunTests(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api):
return _RunTests(input_api, output_api)

File diff suppressed because it is too large Load Diff

@ -2,22 +2,14 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This script is wrapper for Chromium that adds some support for how GYP
is invoked by Chromium beyond what can be done in the gclient hooks.
"""
"""This script is now only used by the closure_compilation builders."""
import argparse
import gc
import glob
import gyp_environment
import mac_toolchain
import os
import re
import shlex
import subprocess
import string
import sys
import vs_toolchain
script_dir = os.path.dirname(os.path.realpath(__file__))
chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
@ -25,52 +17,6 @@ chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
import gyp
# Assume this file is in a one-level-deep subdirectory of the source root.
SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Add paths so that pymod_do_main(...) can import files.
sys.path.insert(1, os.path.join(chrome_src, 'android_webview', 'tools'))
sys.path.insert(1, os.path.join(chrome_src, 'build', 'android', 'gyp'))
sys.path.insert(1, os.path.join(chrome_src, 'chrome', 'tools', 'build'))
sys.path.insert(1, os.path.join(chrome_src, 'chromecast', 'tools', 'build'))
sys.path.insert(1, os.path.join(chrome_src, 'ios', 'chrome', 'tools', 'build'))
sys.path.insert(1, os.path.join(chrome_src, 'native_client', 'build'))
sys.path.insert(1, os.path.join(chrome_src, 'native_client_sdk', 'src',
'build_tools'))
sys.path.insert(1, os.path.join(chrome_src, 'remoting', 'tools', 'build'))
sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'liblouis'))
sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'WebKit',
'Source', 'build', 'scripts'))
sys.path.insert(1, os.path.join(chrome_src, 'build'))
sys.path.insert(1, os.path.join(chrome_src, 'tools'))
sys.path.insert(1, os.path.join(chrome_src, 'tools', 'generate_shim_headers'))
sys.path.insert(1, os.path.join(chrome_src, 'tools', 'grit'))
# On Windows, Psyco shortens warm runs of build/gyp_chromium by about
# 20 seconds on a z600 machine with 12 GB of RAM, from 90 down to 70
# seconds. Conversely, memory usage of build/gyp_chromium with Psyco
# maxes out at about 158 MB vs. 132 MB without it.
#
# Psyco uses native libraries, so we need to load a different
# installation depending on which OS we are running under. It has not
# been tested whether using Psyco on our Mac and Linux builds is worth
# it (the GYP running time is a lot shorter, so the JIT startup cost
# may not be worth it).
if sys.platform == 'win32':
try:
sys.path.insert(0, os.path.join(chrome_src, 'third_party', 'psyco_win32'))
import psyco
except:
psyco = None
else:
psyco = None
def GetSupplementalFiles():
"""Returns a list of the supplemental files that are included in all GYP
sources."""
return glob.glob(os.path.join(chrome_src, '*', 'supplement.gypi'))
def ProcessGypDefinesItems(items):
"""Converts a list of strings to a list of key-value pairs."""
@ -88,43 +34,12 @@ def ProcessGypDefinesItems(items):
return result
def GetGypVars(supplemental_files):
def GetSupplementalFiles():
return []
def GetGypVars(_):
"""Returns a dictionary of all GYP vars."""
# Find the .gyp directory in the user's home directory.
home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None)
if home_dot_gyp:
home_dot_gyp = os.path.expanduser(home_dot_gyp)
if not home_dot_gyp:
home_vars = ['HOME']
if sys.platform in ('cygwin', 'win32'):
home_vars.append('USERPROFILE')
for home_var in home_vars:
home = os.getenv(home_var)
if home != None:
home_dot_gyp = os.path.join(home, '.gyp')
if not os.path.exists(home_dot_gyp):
home_dot_gyp = None
else:
break
if home_dot_gyp:
include_gypi = os.path.join(home_dot_gyp, "include.gypi")
if os.path.exists(include_gypi):
supplemental_files += [include_gypi]
# GYP defines from the supplemental.gypi files.
supp_items = []
for supplement in supplemental_files:
with open(supplement, 'r') as f:
try:
file_data = eval(f.read(), {'__builtins__': None}, None)
except SyntaxError, e:
e.filename = os.path.abspath(supplement)
raise
variables = file_data.get('variables', [])
for v in variables:
supp_items += [(v, str(variables[v]))]
# GYP defines from the environment.
env_items = ProcessGypDefinesItems(
shlex.split(os.environ.get('GYP_DEFINES', '')))
@ -135,227 +50,19 @@ def GetGypVars(supplemental_files):
cmdline_input_items = parser.parse_known_args()[0].defines
cmdline_items = ProcessGypDefinesItems(cmdline_input_items)
vars_dict = dict(supp_items + env_items + cmdline_items)
return vars_dict
def GetOutputDirectory():
"""Returns the output directory that GYP will use."""
# Handle command line generator flags.
parser = argparse.ArgumentParser()
parser.add_argument('-G', dest='genflags', default=[], action='append')
genflags = parser.parse_known_args()[0].genflags
# Handle generator flags from the environment.
genflags += shlex.split(os.environ.get('GYP_GENERATOR_FLAGS', ''))
needle = 'output_dir='
for item in genflags:
if item.startswith(needle):
return item[len(needle):]
return 'out'
def additional_include_files(supplemental_files, args=[]):
"""
Returns a list of additional (.gypi) files to include, without duplicating
ones that are already specified on the command line. The list of supplemental
include files is passed in as an argument.
"""
# Determine the include files specified on the command line.
# This doesn't cover all the different option formats you can use,
# but it's mainly intended to avoid duplicating flags on the automatic
# makefile regeneration which only uses this format.
specified_includes = set()
for arg in args:
if arg.startswith('-I') and len(arg) > 2:
specified_includes.add(os.path.realpath(arg[2:]))
result = []
def AddInclude(path):
if os.path.realpath(path) not in specified_includes:
result.append(path)
if os.environ.get('GYP_INCLUDE_FIRST') != None:
AddInclude(os.path.join(chrome_src, os.environ.get('GYP_INCLUDE_FIRST')))
# Always include common.gypi.
AddInclude(os.path.join(script_dir, 'common.gypi'))
# Optionally add supplemental .gypi files if present.
for supplement in supplemental_files:
AddInclude(supplement)
if os.environ.get('GYP_INCLUDE_LAST') != None:
AddInclude(os.path.join(chrome_src, os.environ.get('GYP_INCLUDE_LAST')))
return result
return dict(env_items + cmdline_items)
def main():
# Disabling garbage collection saves about 1 second out of 16 on a Linux
# z620 workstation. Since this is a short-lived process it's not a problem to
# leak a few cyclyc references in order to spare the CPU cycles for
# scanning the heap.
gc.disable()
args = sys.argv[1:]
use_analyzer = len(args) and args[0] == '--analyzer'
if use_analyzer:
args.pop(0)
os.environ['GYP_GENERATORS'] = 'analyzer'
args.append('-Gconfig_path=' + args.pop(0))
args.append('-Ganalyzer_output_path=' + args.pop(0))
gyp_chromium_no_action = os.environ.get('GYP_CHROMIUM_NO_ACTION')
if gyp_chromium_no_action == '1':
print 'Skipping gyp_chromium due to GYP_CHROMIUM_NO_ACTION env var.'
sys.exit(0)
# Use the Psyco JIT if available.
if psyco:
psyco.profile()
print "Enabled Psyco JIT."
# Fall back on hermetic python if we happen to get run under cygwin.
# TODO(bradnelson): take this out once this issue is fixed:
# http://code.google.com/p/gyp/issues/detail?id=177
if sys.platform == 'cygwin':
import find_depot_tools
depot_tools_path = find_depot_tools.add_depot_tools_to_path()
python_dir = sorted(glob.glob(os.path.join(depot_tools_path,
'python2*_bin')))[-1]
env = os.environ.copy()
env['PATH'] = python_dir + os.pathsep + env.get('PATH', '')
cmd = [os.path.join(python_dir, 'python.exe')] + sys.argv
sys.exit(subprocess.call(cmd, env=env))
# This could give false positives since it doesn't actually do real option
# parsing. Oh well.
gyp_file_specified = any(arg.endswith('.gyp') for arg in args)
gyp_environment.SetEnvironment()
# If we didn't get a file, check an env var, and then fall back to
# assuming 'all.gyp' from the same directory as the script.
if not gyp_file_specified:
gyp_file = os.environ.get('CHROMIUM_GYP_FILE')
if gyp_file:
# Note that CHROMIUM_GYP_FILE values can't have backslashes as
# path separators even on Windows due to the use of shlex.split().
args.extend(shlex.split(gyp_file))
else:
args.append(os.path.join(script_dir, 'all.gyp'))
supplemental_includes = GetSupplementalFiles()
gyp_vars_dict = GetGypVars(supplemental_includes)
# There shouldn't be a circular dependency relationship between .gyp files,
# but in Chromium's .gyp files, on non-Mac platforms, circular relationships
# currently exist. The check for circular dependencies is currently
# bypassed on other platforms, but is left enabled on iOS, where a violation
# of the rule causes Xcode to misbehave badly.
# TODO(mark): Find and kill remaining circular dependencies, and remove this
# option. http://crbug.com/35878.
# TODO(tc): Fix circular dependencies in ChromiumOS then add linux2 to the
# list.
if gyp_vars_dict.get('OS') != 'ios':
args.append('--no-circular-check')
# libtool on Mac warns about duplicate basenames in static libraries, so
# they're disallowed in general by gyp. We are lax on this point, so disable
# this check other than on Mac. GN does not use static libraries as heavily,
# so over time this restriction will mostly go away anyway, even on Mac.
# https://code.google.com/p/gyp/issues/detail?id=384
if sys.platform != 'darwin':
args.append('--no-duplicate-basename-check')
# We explicitly don't support the make gyp generator (crbug.com/348686). Be
# nice and fail here, rather than choking in gyp.
if re.search(r'(^|,|\s)make($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
print 'Error: make gyp generator not supported (check GYP_GENERATORS).'
sys.exit(1)
# We explicitly don't support the native msvs gyp generator. Be nice and
# fail here, rather than generating broken projects.
if re.search(r'(^|,|\s)msvs($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
print 'Error: msvs gyp generator not supported (check GYP_GENERATORS).'
print 'Did you mean to use the `msvs-ninja` generator?'
sys.exit(1)
# We explicitly don't support the native xcode gyp generator. Be nice and
# fail here, rather than generating broken projects.
if re.search(r'(^|,|\s)xcode($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
print 'Error: xcode gyp generator not supported (check GYP_GENERATORS).'
print 'Did you mean to use the `xcode-ninja` generator?'
sys.exit(1)
# If CHROMIUM_GYP_SYNTAX_CHECK is set to 1, it will invoke gyp with --check
# to enfore syntax checking.
syntax_check = os.environ.get('CHROMIUM_GYP_SYNTAX_CHECK')
if syntax_check and int(syntax_check):
args.append('--check')
# TODO(dmikurube): Remove these checks and messages after a while.
if ('linux_use_tcmalloc' in gyp_vars_dict or
'android_use_tcmalloc' in gyp_vars_dict):
print '*****************************************************************'
print '"linux_use_tcmalloc" and "android_use_tcmalloc" are deprecated!'
print '-----------------------------------------------------------------'
print 'You specify "linux_use_tcmalloc" or "android_use_tcmalloc" in'
print 'your GYP_DEFINES. Please switch them into "use_allocator" now.'
print 'See http://crbug.com/345554 for the details.'
print '*****************************************************************'
# Automatically turn on crosscompile support for platforms that need it.
# (The Chrome OS build sets CC_host / CC_target which implicitly enables
# this mode.)
if all(('ninja' in os.environ.get('GYP_GENERATORS', ''),
gyp_vars_dict.get('OS') in ['android', 'ios'],
'GYP_CROSSCOMPILE' not in os.environ)):
os.environ['GYP_CROSSCOMPILE'] = '1'
if gyp_vars_dict.get('OS') == 'android':
args.append('--check')
args.extend(
['-I' + i for i in additional_include_files(supplemental_includes, args)])
args.extend(['-D', 'gyp_output_dir=' + GetOutputDirectory()])
mac_toolchain_dir = mac_toolchain.GetToolchainDirectory()
if mac_toolchain_dir:
args.append('-Gmac_toolchain_dir=' + mac_toolchain_dir)
mac_toolchain.SetToolchainEnvironment()
running_as_hook = '--running-as-hook'
if running_as_hook in args and gyp_chromium_no_action != '0':
print 'GYP is now disabled by default in runhooks.\n'
print 'If you really want to run this, either run '
print '`python build/gyp_chromium.py` explicitly by hand'
print 'or set the environment variable GYP_CHROMIUM_NO_ACTION=0.'
sys.exit(0)
if running_as_hook in args:
args.remove(running_as_hook)
if not use_analyzer:
print 'Updating projects from gyp files...'
sys.stdout.flush()
# Off we go...
gyp_rc = gyp.main(args)
if gyp_rc == 0 and not use_analyzer:
vs2013_runtime_dll_dirs = vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
if vs2013_runtime_dll_dirs:
x64_runtime, x86_runtime = vs2013_runtime_dll_dirs
vs_toolchain.CopyVsRuntimeDlls(
os.path.join(chrome_src, GetOutputDirectory()),
(x86_runtime, x64_runtime))
sys.exit(gyp_rc)
print 'Updating projects from gyp files...'
sys.stdout.flush()
sys.exit(gyp.main(sys.argv[1:] + [
'--check',
'--no-circular-check',
'-I', os.path.join(script_dir, 'common.gypi'),
'-D', 'gyp_output_dir=out']))
if __name__ == '__main__':
sys.exit(main())

@ -1,64 +0,0 @@
#!/usr/bin/env python
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
import unittest
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
SRC_DIR = os.path.dirname(SCRIPT_DIR)
sys.path.append(os.path.join(SRC_DIR, 'third_party', 'pymock'))
import mock
import gyp_chromium
class TestGetOutputDirectory(unittest.TestCase):
@mock.patch('os.environ', {})
@mock.patch('sys.argv', [__file__])
def testDefaultValue(self):
self.assertEqual(gyp_chromium.GetOutputDirectory(), 'out')
@mock.patch('os.environ', {'GYP_GENERATOR_FLAGS': 'output_dir=envfoo'})
@mock.patch('sys.argv', [__file__])
def testEnvironment(self):
self.assertEqual(gyp_chromium.GetOutputDirectory(), 'envfoo')
@mock.patch('os.environ', {'GYP_GENERATOR_FLAGS': 'output_dir=envfoo'})
@mock.patch('sys.argv', [__file__, '-Goutput_dir=cmdfoo'])
def testGFlagOverridesEnv(self):
self.assertEqual(gyp_chromium.GetOutputDirectory(), 'cmdfoo')
@mock.patch('os.environ', {})
@mock.patch('sys.argv', [__file__, '-G', 'output_dir=foo'])
def testGFlagWithSpace(self):
self.assertEqual(gyp_chromium.GetOutputDirectory(), 'foo')
class TestGetGypVars(unittest.TestCase):
@mock.patch('os.environ', {})
def testDefault(self):
self.assertEqual(gyp_chromium.GetGypVars([]), {})
@mock.patch('os.environ', {})
@mock.patch('sys.argv', [__file__, '-D', 'foo=bar'])
def testDFlags(self):
self.assertEqual(gyp_chromium.GetGypVars([]), {'foo': 'bar'})
@mock.patch('os.environ', {})
@mock.patch('sys.argv', [__file__, '-D', 'foo'])
def testDFlagsNoValue(self):
self.assertEqual(gyp_chromium.GetGypVars([]), {'foo': '1'})
@mock.patch('os.environ', {})
@mock.patch('sys.argv', [__file__, '-D', 'foo=bar', '-Dbaz'])
def testDFlagMulti(self):
self.assertEqual(gyp_chromium.GetGypVars([]), {'foo': 'bar', 'baz': '1'})
if __name__ == '__main__':
unittest.main()

@ -39,32 +39,6 @@ You can also just use the current released version of ccache (3.1.8 or 3.1.9)
and disable the chromium style plugin with `clang_use_chrome_plugins=0` in your
`GYP_DEFINES`.
## Use with GYP
We have to set two environment variables (`CC` and `CXX`) before calling
`gclient runhooks` or `build/gyp_chromium`, given you are currently in the
`chromium/src` directory:
```shell
export CC="ccache clang -Qunused-arguments"
export CXX="ccache clang++ -Qunused-arguments"
```
Then run:
```shell
GYP_GENERATORS="ninja" ./build/gyp_chromium
```
or
```shell
GYP_GENERATORS="ninja" gclient runhooks
```
(Instead of relying on the clang/clang++ for building chromium in your `$PATH`,
you can also use the absolute path here.)
## Use with GN
You just need to set the use\_ccache variable. Do so like the following:

@ -77,12 +77,6 @@ http://androidmuscle.com/how-to-enable-usb-debugging-developer-options-on-nexus-
Rebuild `content_shell_apk` with profiling enabled.
With GYP (deprecated):
export GYP_DEFINES="$GYP_DEFINES profiling=1"
build/gyp_chromium
ninja -C out/Release content_shell_apk
With GN:
gn args out/Profiling
@ -197,8 +191,6 @@ Run the following:
1. If you dont see chromium/webkit symbols, make sure that you built/pushed
Release, and that the symlink you created to the .so is valid!
1. If you have symbols, but your callstacks are nonsense, make sure you ran
`build/gyp_chromium` after setting `profiling=1`, and rebuilt.
## Add symbols for the kernel

@ -1,411 +0,0 @@
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Given the output of -t commands from a ninja build for a gyp and GN generated
build, report on differences between the command lines.
This script is best used interactively. Here's a recommended setup:
$ PYTHONPATH=tools/gn/bin python
>>> import sys
>>> import pprint
>>> sys.displayhook = pprint.pprint
>>> import gyp_flag_compare as fc
>>> fc.ConfigureBuild(['gyp_define=1', 'define=2'], ['gn_arg=1', 'arg=2'])
>>> chrome = fc.Comparison('chrome')
The above starts interactive Python, sets up the output to be pretty-printed
(useful for making lists, dicts, and sets readable), configures the build with
GN arguments and GYP defines, and then generates a comparison for that build
configuration for the "chrome" target.
After that, the |chrome| object can be used to investigate differences in the
build.
To configure an official build, use this configuration. Disabling NaCl produces
a more meaningful comparison, as certain files need to get compiled twice
for the IRT build, which uses different flags:
>>> fc.ConfigureBuild(
['disable_nacl=1', 'buildtype=Official', 'branding=Chrome'],
['enable_nacl=false', 'is_official_build=true',
'is_chrome_branded=true'])
"""
import os
import shlex
import subprocess
import sys
# Must be in src/.
os.chdir(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
_GN_OUT_DIR = 'out/gn_flags'
_GYP_OUT_DIR = 'out_gyp_flags/Release'
def ConfigureBuild(gyp_args=[], gn_args=[]):
print >> sys.stderr, 'Regenerating in %s...' % _GN_OUT_DIR
# Currently only Release, non-component.
Run('gn gen %s --args="is_debug=false is_component_build=false %s"' % \
(_GN_OUT_DIR, ' '.join(gn_args)))
os.environ.pop('GYP_DEFINES', None)
# Remove environment variables required by gn but conflicting with GYP.
# Relevant if Windows toolchain isn't provided by depot_tools.
os.environ.pop('GYP_MSVS_OVERRIDE_PATH', None)
os.environ.pop('WINDOWSSDKDIR', None)
gyp_defines = ''
if len(gyp_args) > 0:
gyp_defines = '-D' + ' -D'.join(gyp_args)
print >> sys.stderr, 'Regenerating in %s...' % _GYP_OUT_DIR
Run('python build/gyp_chromium -Goutput_dir=out_gyp_flags ' + \
'-Gconfig=Release %s' % gyp_defines)
def Counts(dict_of_list):
"""Given a dictionary whose value are lists, returns a dictionary whose values
are the length of the list. This can be used to summarize a dictionary.
"""
return {k: len(v) for k, v in dict_of_list.iteritems()}
def CountsByDirname(dict_of_list):
"""Given a list of files, returns a dict of dirname to counts in that dir."""
r = {}
for path in dict_of_list:
dirname = os.path.dirname(path)
r.setdefault(dirname, 0)
r[dirname] += 1
return r
class Comparison(object):
"""A comparison of the currently-configured build for a target."""
def __init__(self, gyp_target, gn_target=None):
"""Creates a comparison of a GN and GYP target. If the target names differ
between the two build systems, then two names may be passed.
"""
if gn_target is None:
gn_target = gyp_target
self._gyp_target = gyp_target
self._gn_target = gn_target
self._skipped = []
self._total_diffs = 0
self._missing_gyp_flags = {}
self._missing_gn_flags = {}
self._missing_gyp_files = {}
self._missing_gn_files = {}
self._CompareFiles()
@property
def gyp_files(self):
"""Returns the set of files that are in the GYP target."""
return set(self._gyp_flags.keys())
@property
def gn_files(self):
"""Returns the set of files that are in the GN target."""
return set(self._gn_flags.keys())
@property
def skipped(self):
"""Returns the list of compiler commands that were not processed during the
comparison.
"""
return self._skipped
@property
def total_differences(self):
"""Returns the total number of differences detected."""
return self._total_diffs
@property
def missing_in_gyp(self):
"""Differences that are only in GYP build but not in GN, indexed by the
difference."""
return self._missing_gyp_flags
@property
def missing_in_gn(self):
"""Differences that are only in the GN build but not in GYP, indexed by
the difference."""
return self._missing_gn_flags
@property
def missing_in_gyp_by_file(self):
"""Differences that are only in the GYP build but not in GN, indexed by
file.
"""
return self._missing_gyp_files
@property
def missing_in_gn_by_file(self):
"""Differences that are only in the GYP build but not in GN, indexed by
file.
"""
return self._missing_gn_files
def _CompareFiles(self):
"""Performs the actual target comparison."""
if sys.platform == 'win32':
# On Windows flags are stored in .rsp files which are created by building.
print >> sys.stderr, 'Building in %s...' % _GN_OUT_DIR
Run('ninja -C %s -d keeprsp %s' % (_GN_OUT_DIR, self._gn_target))
print >> sys.stderr, 'Building in %s...' % _GYP_OUT_DIR
Run('ninja -C %s -d keeprsp %s' % (_GYP_OUT_DIR, self._gn_target))
gn = Run('ninja -C %s -t commands %s' % (_GN_OUT_DIR, self._gn_target))
gyp = Run('ninja -C %s -t commands %s' % (_GYP_OUT_DIR, self._gyp_target))
self._gn_flags = self._GetFlags(gn.splitlines(),
os.path.join(os.getcwd(), _GN_OUT_DIR))
self._gyp_flags = self._GetFlags(gyp.splitlines(),
os.path.join(os.getcwd(), _GYP_OUT_DIR))
all_files = sorted(self.gn_files & self.gyp_files)
for filename in all_files:
gyp_flags = self._gyp_flags[filename]
gn_flags = self._gn_flags[filename]
self._CompareLists(filename, gyp_flags, gn_flags, 'dash_f')
self._CompareLists(filename, gyp_flags, gn_flags, 'defines')
self._CompareLists(filename, gyp_flags, gn_flags, 'include_dirs')
self._CompareLists(filename, gyp_flags, gn_flags, 'warnings',
# More conservative warnings in GN we consider to be OK.
dont_care_gyp=[
'/wd4091', # 'keyword' : ignored on left of 'type' when no variable
# is declared.
'/wd4456', # Declaration hides previous local declaration.
'/wd4457', # Declaration hides function parameter.
'/wd4458', # Declaration hides class member.
'/wd4459', # Declaration hides global declaration.
'/wd4702', # Unreachable code.
'/wd4800', # Forcing value to bool 'true' or 'false'.
'/wd4838', # Conversion from 'type' to 'type' requires a narrowing
# conversion.
] if sys.platform == 'win32' else None,
dont_care_gn=[
'-Wendif-labels',
'-Wextra',
'-Wsign-compare',
] if not sys.platform == 'win32' else None)
self._CompareLists(filename, gyp_flags, gn_flags, 'other')
def _CompareLists(self, filename, gyp, gn, name,
dont_care_gyp=None, dont_care_gn=None):
"""Return a report of any differences between gyp and gn lists, ignoring
anything in |dont_care_{gyp|gn}| respectively."""
if gyp[name] == gn[name]:
return
if not dont_care_gyp:
dont_care_gyp = []
if not dont_care_gn:
dont_care_gn = []
gyp_set = set(gyp[name])
gn_set = set(gn[name])
missing_in_gyp = gyp_set - gn_set
missing_in_gn = gn_set - gyp_set
missing_in_gyp -= set(dont_care_gyp)
missing_in_gn -= set(dont_care_gn)
for m in missing_in_gyp:
self._missing_gyp_flags.setdefault(name, {}) \
.setdefault(m, []).append(filename)
self._total_diffs += 1
self._missing_gyp_files.setdefault(filename, {}) \
.setdefault(name, set()).update(missing_in_gyp)
for m in missing_in_gn:
self._missing_gn_flags.setdefault(name, {}) \
.setdefault(m, []).append(filename)
self._total_diffs += 1
self._missing_gn_files.setdefault(filename, {}) \
.setdefault(name, set()).update(missing_in_gn)
def _GetFlags(self, lines, build_dir):
"""Turn a list of command lines into a semi-structured dict."""
is_win = sys.platform == 'win32'
flags_by_output = {}
for line in lines:
command_line = shlex.split(line.strip(), posix=not is_win)[1:]
output_name = _FindAndRemoveArgWithValue(command_line, '-o')
dep_name = _FindAndRemoveArgWithValue(command_line, '-MF')
command_line = _MergeSpacedArgs(command_line, '-Xclang')
cc_file = [x for x in command_line if x.endswith('.cc') or
x.endswith('.c') or
x.endswith('.cpp') or
x.endswith('.mm') or
x.endswith('.m')]
if len(cc_file) != 1:
self._skipped.append(command_line)
continue
assert len(cc_file) == 1
if is_win:
rsp_file = [x for x in command_line if x.endswith('.rsp')]
assert len(rsp_file) <= 1
if rsp_file:
rsp_file = os.path.join(build_dir, rsp_file[0][1:])
with open(rsp_file, "r") as open_rsp_file:
command_line = shlex.split(open_rsp_file, posix=False)
defines = [x for x in command_line if x.startswith('-D')]
include_dirs = [x for x in command_line if x.startswith('-I')]
dash_f = [x for x in command_line if x.startswith('-f')]
warnings = \
[x for x in command_line if x.startswith('/wd' if is_win else '-W')]
others = [x for x in command_line if x not in defines and \
x not in include_dirs and \
x not in dash_f and \
x not in warnings and \
x not in cc_file]
for index, value in enumerate(include_dirs):
if value == '-Igen':
continue
path = value[2:]
if not os.path.isabs(path):
path = os.path.join(build_dir, path)
include_dirs[index] = '-I' + os.path.normpath(path)
# GYP supports paths above the source root like <(DEPTH)/../foo while such
# paths are unsupported by gn. But gn allows to use system-absolute paths
# instead (paths that start with single '/'). Normalize all paths.
cc_file = [os.path.normpath(os.path.join(build_dir, cc_file[0]))]
# Filter for libFindBadConstructs.so having a relative path in one and
# absolute path in the other.
others_filtered = []
for x in others:
if x.startswith('-Xclang ') and \
(x.endswith('libFindBadConstructs.so') or \
x.endswith('libFindBadConstructs.dylib')):
others_filtered.append(
'-Xclang ' +
os.path.join(os.getcwd(), os.path.normpath(
os.path.join('out/gn_flags', x.split(' ', 1)[1]))))
elif x.startswith('-B'):
others_filtered.append(
'-B' +
os.path.join(os.getcwd(), os.path.normpath(
os.path.join('out/gn_flags', x[2:]))))
else:
others_filtered.append(x)
others = others_filtered
flags_by_output[cc_file[0]] = {
'output': output_name,
'depname': dep_name,
'defines': sorted(defines),
'include_dirs': sorted(include_dirs), # TODO(scottmg): This is wrong.
'dash_f': sorted(dash_f),
'warnings': sorted(warnings),
'other': sorted(others),
}
return flags_by_output
def _FindAndRemoveArgWithValue(command_line, argname):
"""Given a command line as a list, remove and return the value of an option
that takes a value as a separate entry.
Modifies |command_line| in place.
"""
if argname not in command_line:
return ''
location = command_line.index(argname)
value = command_line[location + 1]
command_line[location:location + 2] = []
return value
def _MergeSpacedArgs(command_line, argname):
"""Combine all arguments |argname| with their values, separated by a space."""
i = 0
result = []
while i < len(command_line):
arg = command_line[i]
if arg == argname:
result.append(arg + ' ' + command_line[i + 1])
i += 1
else:
result.append(arg)
i += 1
return result
def Run(command_line):
"""Run |command_line| as a subprocess and return stdout. Raises on error."""
print >> sys.stderr, command_line
return subprocess.check_output(command_line, shell=True)
def main():
if len(sys.argv) != 2 and len(sys.argv) != 3:
print 'usage: %s gyp_target gn_target' % __file__
print ' or: %s target' % __file__
return 1
ConfigureBuild()
gyp_target = sys.argv[1]
if len(sys.argv) == 2:
gn_target = gyp_target
else:
gn_target = sys.argv[2]
comparison = Comparison(gyp_target, gn_target)
gyp_files = comparison.gyp_files
gn_files = comparison.gn_files
different_source_list = comparison.gyp_files != comparison.gn_files
if different_source_list:
print 'Different set of sources files:'
print ' In gyp, not in GN:\n %s' % '\n '.join(
sorted(gyp_files - gn_files))
print ' In GN, not in gyp:\n %s' % '\n '.join(
sorted(gn_files - gyp_files))
print '\nNote that flags will only be compared for files in both sets.\n'
differing_files = set(comparison.missing_in_gn_by_file.keys()) & \
set(comparison.missing_in_gyp_by_file.keys())
files_with_given_differences = {}
for filename in differing_files:
output = ''
missing_in_gyp = comparison.missing_in_gyp_by_file.get(filename, {})
missing_in_gn = comparison.missing_in_gn_by_file.get(filename, {})
difference_types = sorted(set(missing_in_gyp.keys() + missing_in_gn.keys()))
for difference_type in difference_types:
output += ' %s differ:\n' % difference_type
if difference_type in missing_in_gyp:
output += ' In gyp, but not in GN:\n %s' % '\n '.join(
sorted(missing_in_gyp[difference_type])) + '\n'
if difference_type in missing_in_gn:
output += ' In GN, but not in gyp:\n %s' % '\n '.join(
sorted(missing_in_gn[difference_type])) + '\n'
if output:
files_with_given_differences.setdefault(output, []).append(filename)
for diff, files in files_with_given_differences.iteritems():
print '\n'.join(sorted(files))
print diff
print 'Total differences:', comparison.total_differences
# TODO(scottmg): Return failure on difference once we're closer to identical.
return 0
if __name__ == '__main__':
sys.exit(main())

@ -1,119 +0,0 @@
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Prints paths between gyp targets.
"""
import json
import os
import sys
import time
from collections import deque
def usage():
print """\
Usage:
tools/gyp-explain.py [--dot] chrome_dll# gtest#
"""
def GetPath(graph, fro, to):
"""Given a graph in (node -> list of successor nodes) dictionary format,
yields all paths from |fro| to |to|, starting with the shortest."""
# Storing full paths in the queue is a bit wasteful, but good enough for this.
q = deque([(fro, [])])
while q:
t, path = q.popleft()
if t == to:
yield path + [t]
for d in graph[t]:
q.append((d, path + [t]))
def MatchNode(graph, substring):
"""Given a dictionary, returns the key that matches |substring| best. Exits
if there's not one single best match."""
candidates = []
for target in graph:
if substring in target:
candidates.append(target)
if not candidates:
print 'No targets match "%s"' % substring
sys.exit(1)
if len(candidates) > 1:
print 'More than one target matches "%s": %s' % (
substring, ' '.join(candidates))
sys.exit(1)
return candidates[0]
def EscapeForDot(string):
suffix = '#target'
if string.endswith(suffix):
string = string[:-len(suffix)]
string = string.replace('\\', '\\\\')
return '"' + string + '"'
def GenerateDot(fro, to, paths):
"""Generates an input file for graphviz's dot program."""
prefixes = [os.path.commonprefix(path) for path in paths]
prefix = os.path.commonprefix(prefixes)
print '// Build with "dot -Tpng -ooutput.png this_file.dot"'
# "strict" collapses common paths.
print 'strict digraph {'
for path in paths:
print (' -> '.join(EscapeForDot(item[len(prefix):]) for item in path)), ';'
print '}'
def Main(argv):
# Check that dump.json exists and that it's not too old.
dump_json_dirty = False
try:
st = os.stat('dump.json')
file_age_s = time.time() - st.st_mtime
if file_age_s > 2 * 60 * 60:
print 'dump.json is more than 2 hours old.'
dump_json_dirty = True
except OSError:
print 'dump.json not found.'
dump_json_dirty = True
if dump_json_dirty:
print 'Run'
print ' GYP_GENERATORS=dump_dependency_json build/gyp_chromium'
print 'first, then try again.'
sys.exit(1)
g = json.load(open('dump.json'))
if len(argv) not in (3, 4):
usage()
sys.exit(1)
generate_dot = argv[1] == '--dot'
if generate_dot:
argv.pop(1)
fro = MatchNode(g, argv[1])
to = MatchNode(g, argv[2])
paths = list(GetPath(g, fro, to))
if len(paths) > 0:
if generate_dot:
GenerateDot(fro, to, paths)
else:
print 'These paths lead from %s to %s:' % (fro, to)
for path in paths:
print ' -> '.join(path)
else:
print 'No paths found from %s to %s.' % (fro, to)
if __name__ == '__main__':
Main(sys.argv)