Home | History | Annotate | Download | only in lib
      1 # Copyright (c) 2012 Google Inc. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """
      6 TestGyp.py:  a testing framework for GYP integration tests.
      7 """
      8 
      9 import collections
     10 import itertools
     11 import os
     12 import re
     13 import shutil
     14 import stat
     15 import subprocess
     16 import sys
     17 import tempfile
     18 
     19 import TestCmd
     20 import TestCommon
     21 from TestCommon import __all__
     22 
     23 __all__.extend([
     24   'TestGyp',
     25 ])
     26 
     27 def remove_debug_line_numbers(contents):
     28   """Function to remove the line numbers from the debug output
     29   of gyp and thus remove the exremem fragility of the stdout
     30   comparison tests.
     31   """
     32   lines = contents.splitlines()
     33   # split each line on ":"
     34   lines = [l.split(":", 3) for l in lines]
     35   # join each line back together while ignoring the
     36   # 3rd column which is the line number
     37   lines = [len(l) > 3 and ":".join(l[3:]) or l for l in lines]
     38   return "\n".join(lines)
     39 
     40 def match_modulo_line_numbers(contents_a, contents_b):
     41   """File contents matcher that ignores line numbers."""
     42   contents_a = remove_debug_line_numbers(contents_a)
     43   contents_b = remove_debug_line_numbers(contents_b)
     44   return TestCommon.match_exact(contents_a, contents_b)
     45 
     46 class TestGypBase(TestCommon.TestCommon):
     47   """
     48   Class for controlling end-to-end tests of gyp generators.
     49 
     50   Instantiating this class will create a temporary directory and
     51   arrange for its destruction (via the TestCmd superclass) and
     52   copy all of the non-gyptest files in the directory hierarchy of the
     53   executing script.
     54 
     55   The default behavior is to test the 'gyp' or 'gyp.bat' file in the
     56   current directory.  An alternative may be specified explicitly on
     57   instantiation, or by setting the TESTGYP_GYP environment variable.
     58 
     59   This class should be subclassed for each supported gyp generator
     60   (format).  Various abstract methods below define calling signatures
     61   used by the test scripts to invoke builds on the generated build
     62   configuration and to run executables generated by those builds.
     63   """
     64 
     65   build_tool = None
     66   build_tool_list = []
     67 
     68   _exe = TestCommon.exe_suffix
     69   _obj = TestCommon.obj_suffix
     70   shobj_ = TestCommon.shobj_prefix
     71   _shobj = TestCommon.shobj_suffix
     72   lib_ = TestCommon.lib_prefix
     73   _lib = TestCommon.lib_suffix
     74   dll_ = TestCommon.dll_prefix
     75   _dll = TestCommon.dll_suffix
     76 
     77   # Constants to represent different targets.
     78   ALL = '__all__'
     79   DEFAULT = '__default__'
     80 
     81   # Constants for different target types.
     82   EXECUTABLE = '__executable__'
     83   STATIC_LIB = '__static_lib__'
     84   SHARED_LIB = '__shared_lib__'
     85 
     86   def __init__(self, gyp=None, *args, **kw):
     87     self.origin_cwd = os.path.abspath(os.path.dirname(sys.argv[0]))
     88     self.extra_args = sys.argv[1:]
     89 
     90     if not gyp:
     91       gyp = os.environ.get('TESTGYP_GYP')
     92       if not gyp:
     93         if sys.platform == 'win32':
     94           gyp = 'gyp.bat'
     95         else:
     96           gyp = 'gyp'
     97     self.gyp = os.path.abspath(gyp)
     98     self.no_parallel = False
     99 
    100     self.initialize_build_tool()
    101 
    102     kw.setdefault('match', TestCommon.match_exact)
    103 
    104     # Put test output in out/testworkarea by default.
    105     # Use temporary names so there are no collisions.
    106     workdir = os.path.join('out', kw.get('workdir', 'testworkarea'))
    107     # Create work area if it doesn't already exist.
    108     if not os.path.isdir(workdir):
    109       os.makedirs(workdir)
    110 
    111     kw['workdir'] = tempfile.mktemp(prefix='testgyp.', dir=workdir)
    112 
    113     formats = kw.pop('formats', [])
    114 
    115     super(TestGypBase, self).__init__(*args, **kw)
    116 
    117     excluded_formats = set([f for f in formats if f[0] == '!'])
    118     included_formats = set(formats) - excluded_formats
    119     if ('!'+self.format in excluded_formats or
    120         included_formats and self.format not in included_formats):
    121       msg = 'Invalid test for %r format; skipping test.\n'
    122       self.skip_test(msg % self.format)
    123 
    124     self.copy_test_configuration(self.origin_cwd, self.workdir)
    125     self.set_configuration(None)
    126 
    127     # Set $HOME so that gyp doesn't read the user's actual
    128     # ~/.gyp/include.gypi file, which may contain variables
    129     # and other settings that would change the output.
    130     os.environ['HOME'] = self.workpath()
    131     # Clear $GYP_DEFINES for the same reason.
    132     if 'GYP_DEFINES' in os.environ:
    133       del os.environ['GYP_DEFINES']
    134 
    135   def built_file_must_exist(self, name, type=None, **kw):
    136     """
    137     Fails the test if the specified built file name does not exist.
    138     """
    139     return self.must_exist(self.built_file_path(name, type, **kw))
    140 
    141   def built_file_must_not_exist(self, name, type=None, **kw):
    142     """
    143     Fails the test if the specified built file name exists.
    144     """
    145     return self.must_not_exist(self.built_file_path(name, type, **kw))
    146 
    147   def built_file_must_match(self, name, contents, **kw):
    148     """
    149     Fails the test if the contents of the specified built file name
    150     do not match the specified contents.
    151     """
    152     return self.must_match(self.built_file_path(name, **kw), contents)
    153 
    154   def built_file_must_not_match(self, name, contents, **kw):
    155     """
    156     Fails the test if the contents of the specified built file name
    157     match the specified contents.
    158     """
    159     return self.must_not_match(self.built_file_path(name, **kw), contents)
    160 
    161   def built_file_must_not_contain(self, name, contents, **kw):
    162     """
    163     Fails the test if the specified built file name contains the specified
    164     contents.
    165     """
    166     return self.must_not_contain(self.built_file_path(name, **kw), contents)
    167 
    168   def copy_test_configuration(self, source_dir, dest_dir):
    169     """
    170     Copies the test configuration from the specified source_dir
    171     (the directory in which the test script lives) to the
    172     specified dest_dir (a temporary working directory).
    173 
    174     This ignores all files and directories that begin with
    175     the string 'gyptest', and all '.svn' subdirectories.
    176     """
    177     for root, dirs, files in os.walk(source_dir):
    178       if '.svn' in dirs:
    179         dirs.remove('.svn')
    180       dirs = [ d for d in dirs if not d.startswith('gyptest') ]
    181       files = [ f for f in files if not f.startswith('gyptest') ]
    182       for dirname in dirs:
    183         source = os.path.join(root, dirname)
    184         destination = source.replace(source_dir, dest_dir)
    185         os.mkdir(destination)
    186         if sys.platform != 'win32':
    187           shutil.copystat(source, destination)
    188       for filename in files:
    189         source = os.path.join(root, filename)
    190         destination = source.replace(source_dir, dest_dir)
    191         shutil.copy2(source, destination)
    192 
    193   def initialize_build_tool(self):
    194     """
    195     Initializes the .build_tool attribute.
    196 
    197     Searches the .build_tool_list for an executable name on the user's
    198     $PATH.  The first tool on the list is used as-is if nothing is found
    199     on the current $PATH.
    200     """
    201     for build_tool in self.build_tool_list:
    202       if not build_tool:
    203         continue
    204       if os.path.isabs(build_tool):
    205         self.build_tool = build_tool
    206         return
    207       build_tool = self.where_is(build_tool)
    208       if build_tool:
    209         self.build_tool = build_tool
    210         return
    211 
    212     if self.build_tool_list:
    213       self.build_tool = self.build_tool_list[0]
    214 
    215   def relocate(self, source, destination):
    216     """
    217     Renames (relocates) the specified source (usually a directory)
    218     to the specified destination, creating the destination directory
    219     first if necessary.
    220 
    221     Note:  Don't use this as a generic "rename" operation.  In the
    222     future, "relocating" parts of a GYP tree may affect the state of
    223     the test to modify the behavior of later method calls.
    224     """
    225     destination_dir = os.path.dirname(destination)
    226     if not os.path.exists(destination_dir):
    227       self.subdir(destination_dir)
    228     os.rename(source, destination)
    229 
    230   def report_not_up_to_date(self):
    231     """
    232     Reports that a build is not up-to-date.
    233 
    234     This provides common reporting for formats that have complicated
    235     conditions for checking whether a build is up-to-date.  Formats
    236     that expect exact output from the command (make) can
    237     just set stdout= when they call the run_build() method.
    238     """
    239     print "Build is not up-to-date:"
    240     print self.banner('STDOUT ')
    241     print self.stdout()
    242     stderr = self.stderr()
    243     if stderr:
    244       print self.banner('STDERR ')
    245       print stderr
    246 
    247   def run_gyp(self, gyp_file, *args, **kw):
    248     """
    249     Runs gyp against the specified gyp_file with the specified args.
    250     """
    251 
    252     # When running gyp, and comparing its output we use a comparitor
    253     # that ignores the line numbers that gyp logs in its debug output.
    254     if kw.pop('ignore_line_numbers', False):
    255       kw.setdefault('match', match_modulo_line_numbers)
    256 
    257     # TODO:  --depth=. works around Chromium-specific tree climbing.
    258     depth = kw.pop('depth', '.')
    259     run_args = ['--depth='+depth, '--format='+self.format, gyp_file]
    260     if self.no_parallel:
    261       run_args += ['--no-parallel']
    262     run_args.extend(self.extra_args)
    263     run_args.extend(args)
    264     return self.run(program=self.gyp, arguments=run_args, **kw)
    265 
    266   def run(self, *args, **kw):
    267     """
    268     Executes a program by calling the superclass .run() method.
    269 
    270     This exists to provide a common place to filter out keyword
    271     arguments implemented in this layer, without having to update
    272     the tool-specific subclasses or clutter the tests themselves
    273     with platform-specific code.
    274     """
    275     if kw.has_key('SYMROOT'):
    276       del kw['SYMROOT']
    277     super(TestGypBase, self).run(*args, **kw)
    278 
    279   def set_configuration(self, configuration):
    280     """
    281     Sets the configuration, to be used for invoking the build
    282     tool and testing potential built output.
    283     """
    284     self.configuration = configuration
    285 
    286   def configuration_dirname(self):
    287     if self.configuration:
    288       return self.configuration.split('|')[0]
    289     else:
    290       return 'Default'
    291 
    292   def configuration_buildname(self):
    293     if self.configuration:
    294       return self.configuration
    295     else:
    296       return 'Default'
    297 
    298   #
    299   # Abstract methods to be defined by format-specific subclasses.
    300   #
    301 
    302   def build(self, gyp_file, target=None, **kw):
    303     """
    304     Runs a build of the specified target against the configuration
    305     generated from the specified gyp_file.
    306 
    307     A 'target' argument of None or the special value TestGyp.DEFAULT
    308     specifies the default argument for the underlying build tool.
    309     A 'target' argument of TestGyp.ALL specifies the 'all' target
    310     (if any) of the underlying build tool.
    311     """
    312     raise NotImplementedError
    313 
    314   def built_file_path(self, name, type=None, **kw):
    315     """
    316     Returns a path to the specified file name, of the specified type.
    317     """
    318     raise NotImplementedError
    319 
    320   def built_file_basename(self, name, type=None, **kw):
    321     """
    322     Returns the base name of the specified file name, of the specified type.
    323 
    324     A bare=True keyword argument specifies that prefixes and suffixes shouldn't
    325     be applied.
    326     """
    327     if not kw.get('bare'):
    328       if type == self.EXECUTABLE:
    329         name = name + self._exe
    330       elif type == self.STATIC_LIB:
    331         name = self.lib_ + name + self._lib
    332       elif type == self.SHARED_LIB:
    333         name = self.dll_ + name + self._dll
    334     return name
    335 
    336   def run_built_executable(self, name, *args, **kw):
    337     """
    338     Runs an executable program built from a gyp-generated configuration.
    339 
    340     The specified name should be independent of any particular generator.
    341     Subclasses should find the output executable in the appropriate
    342     output build directory, tack on any necessary executable suffix, etc.
    343     """
    344     raise NotImplementedError
    345 
    346   def up_to_date(self, gyp_file, target=None, **kw):
    347     """
    348     Verifies that a build of the specified target is up to date.
    349 
    350     The subclass should implement this by calling build()
    351     (or a reasonable equivalent), checking whatever conditions
    352     will tell it the build was an "up to date" null build, and
    353     failing if it isn't.
    354     """
    355     raise NotImplementedError
    356 
    357 
    358 class TestGypGypd(TestGypBase):
    359   """
    360   Subclass for testing the GYP 'gypd' generator (spit out the
    361   internal data structure as pretty-printed Python).
    362   """
    363   format = 'gypd'
    364   def __init__(self, gyp=None, *args, **kw):
    365     super(TestGypGypd, self).__init__(*args, **kw)
    366     # gypd implies the use of 'golden' files, so parallelizing conflicts as it
    367     # causes ordering changes.
    368     self.no_parallel = True
    369 
    370 
    371 class TestGypCustom(TestGypBase):
    372   """
    373   Subclass for testing the GYP with custom generator
    374   """
    375 
    376   def __init__(self, gyp=None, *args, **kw):
    377     self.format = kw.pop("format")
    378     super(TestGypCustom, self).__init__(*args, **kw)
    379 
    380 
    381 class TestGypAndroid(TestGypBase):
    382   """
    383   Subclass for testing the GYP Android makefile generator. Note that
    384   build/envsetup.sh and lunch must have been run before running tests.
    385 
    386   TODO: This is currently an incomplete implementation. We do not support
    387   run_built_executable(), so we pass only tests which do not use this. As a
    388   result, support for host targets is not properly tested.
    389   """
    390   format = 'android'
    391 
    392   # Note that we can't use mmm as the build tool because ...
    393   # - it builds all targets, whereas we need to pass a target
    394   # - it is a function, whereas the test runner assumes the build tool is a file
    395   # Instead we use make and duplicate the logic from mmm.
    396   build_tool_list = ['make']
    397 
    398   # We use our custom target 'gyp_all_modules', as opposed to the 'all_modules'
    399   # target used by mmm, to build only those targets which are part of the gyp
    400   # target 'all'.
    401   ALL = 'gyp_all_modules'
    402 
    403   def __init__(self, gyp=None, *args, **kw):
    404     # Android requires build and test output to be inside its source tree.
    405     # We use the following working directory for the test's source, but the
    406     # test's build output still goes to $ANDROID_PRODUCT_OUT.
    407     # Note that some tests explicitly set format='gypd' to invoke the gypd
    408     # backend. This writes to the source tree, but there's no way around this.
    409     kw['workdir'] = os.path.join('/tmp', 'gyptest',
    410                                  kw.get('workdir', 'testworkarea'))
    411     # We need to remove all gyp outputs from out/. Ths is because some tests
    412     # don't have rules to regenerate output, so they will simply re-use stale
    413     # output if present. Since the test working directory gets regenerated for
    414     # each test run, this can confuse things.
    415     # We don't have a list of build outputs because we don't know which
    416     # dependent targets were built. Instead we delete all gyp-generated output.
    417     # This may be excessive, but should be safe.
    418     out_dir = os.environ['ANDROID_PRODUCT_OUT']
    419     obj_dir = os.path.join(out_dir, 'obj')
    420     shutil.rmtree(os.path.join(obj_dir, 'GYP'), ignore_errors = True)
    421     for x in ['EXECUTABLES', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES']:
    422       for d in os.listdir(os.path.join(obj_dir, x)):
    423         if d.endswith('_gyp_intermediates'):
    424           shutil.rmtree(os.path.join(obj_dir, x, d), ignore_errors = True)
    425     for x in [os.path.join('obj', 'lib'), os.path.join('system', 'lib')]:
    426       for d in os.listdir(os.path.join(out_dir, x)):
    427         if d.endswith('_gyp.so'):
    428           os.remove(os.path.join(out_dir, x, d))
    429 
    430     super(TestGypAndroid, self).__init__(*args, **kw)
    431 
    432   def target_name(self, target):
    433     if target == self.ALL:
    434       return self.ALL
    435     # The default target is 'droid'. However, we want to use our special target
    436     # to build only the gyp target 'all'.
    437     if target in (None, self.DEFAULT):
    438       return self.ALL
    439     return target
    440 
    441   def build(self, gyp_file, target=None, **kw):
    442     """
    443     Runs a build using the Android makefiles generated from the specified
    444     gyp_file. This logic is taken from Android's mmm.
    445     """
    446     arguments = kw.get('arguments', [])[:]
    447     arguments.append(self.target_name(target))
    448     arguments.append('-C')
    449     arguments.append(os.environ['ANDROID_BUILD_TOP'])
    450     kw['arguments'] = arguments
    451     chdir = kw.get('chdir', '')
    452     makefile = os.path.join(self.workdir, chdir, 'GypAndroid.mk')
    453     os.environ['ONE_SHOT_MAKEFILE'] = makefile
    454     result = self.run(program=self.build_tool, **kw)
    455     del os.environ['ONE_SHOT_MAKEFILE']
    456     return result
    457 
    458   def android_module(self, group, name, subdir):
    459     if subdir:
    460       name = '%s_%s' % (subdir, name)
    461     if group == 'SHARED_LIBRARIES':
    462       name = 'lib_%s' % name
    463     return '%s_gyp' % name
    464 
    465   def intermediates_dir(self, group, module_name):
    466     return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', group,
    467                         '%s_intermediates' % module_name)
    468 
    469   def built_file_path(self, name, type=None, **kw):
    470     """
    471     Returns a path to the specified file name, of the specified type,
    472     as built by Android. Note that we don't support the configuration
    473     parameter.
    474     """
    475     # Built files are in $ANDROID_PRODUCT_OUT. This requires copying logic from
    476     # the Android build system.
    477     if type == None:
    478       return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', 'GYP',
    479                           'shared_intermediates', name)
    480     subdir = kw.get('subdir')
    481     if type == self.EXECUTABLE:
    482       # We don't install executables
    483       group = 'EXECUTABLES'
    484       module_name = self.android_module(group, name, subdir)
    485       return os.path.join(self.intermediates_dir(group, module_name), name)
    486     if type == self.STATIC_LIB:
    487       group = 'STATIC_LIBRARIES'
    488       module_name = self.android_module(group, name, subdir)
    489       return os.path.join(self.intermediates_dir(group, module_name),
    490                           '%s.a' % module_name)
    491     if type == self.SHARED_LIB:
    492       group = 'SHARED_LIBRARIES'
    493       module_name = self.android_module(group, name, subdir)
    494       return os.path.join(self.intermediates_dir(group, module_name), 'LINKED',
    495                           '%s.so' % module_name)
    496     assert False, 'Unhandled type'
    497 
    498   def run_built_executable(self, name, *args, **kw):
    499     """
    500     Runs an executable program built from a gyp-generated configuration.
    501 
    502     This is not correctly implemented for Android. For now, we simply check
    503     that the executable file exists.
    504     """
    505     # Running executables requires a device. Even if we build for target x86,
    506     # the binary is not built with the correct toolchain options to actually
    507     # run on the host.
    508 
    509     # Copied from TestCommon.run()
    510     match = kw.pop('match', self.match)
    511     status = None
    512     if os.path.exists(self.built_file_path(name)):
    513       status = 1
    514     self._complete(None, None, None, None, status, match)
    515 
    516   def match_single_line(self, lines = None, expected_line = None):
    517     """
    518     Checks that specified line appears in the text.
    519     """
    520     for line in lines.split('\n'):
    521         if line == expected_line:
    522             return 1
    523     return
    524 
    525   def up_to_date(self, gyp_file, target=None, **kw):
    526     """
    527     Verifies that a build of the specified target is up to date.
    528     """
    529     kw['stdout'] = ("make: Nothing to be done for `%s'." %
    530                     self.target_name(target))
    531 
    532     # We need to supply a custom matcher, since we don't want to depend on the
    533     # exact stdout string.
    534     kw['match'] = self.match_single_line
    535     return self.build(gyp_file, target, **kw)
    536 
    537 
    538 class TestGypCMake(TestGypBase):
    539   """
    540   Subclass for testing the GYP CMake generator, using cmake's ninja backend.
    541   """
    542   format = 'cmake'
    543   build_tool_list = ['cmake']
    544   ALL = 'all'
    545 
    546   def cmake_build(self, gyp_file, target=None, **kw):
    547     arguments = kw.get('arguments', [])[:]
    548 
    549     self.build_tool_list = ['cmake']
    550     self.initialize_build_tool()
    551 
    552     chdir = os.path.join(kw.get('chdir', '.'),
    553                          'out',
    554                          self.configuration_dirname())
    555     kw['chdir'] = chdir
    556 
    557     arguments.append('-G')
    558     arguments.append('Ninja')
    559 
    560     kw['arguments'] = arguments
    561 
    562     stderr = kw.get('stderr', None)
    563     if stderr:
    564       kw['stderr'] = stderr.split('$$$')[0]
    565 
    566     self.run(program=self.build_tool, **kw)
    567 
    568   def ninja_build(self, gyp_file, target=None, **kw):
    569     arguments = kw.get('arguments', [])[:]
    570 
    571     self.build_tool_list = ['ninja']
    572     self.initialize_build_tool()
    573 
    574     # Add a -C output/path to the command line.
    575     arguments.append('-C')
    576     arguments.append(os.path.join('out', self.configuration_dirname()))
    577 
    578     if target not in (None, self.DEFAULT):
    579       arguments.append(target)
    580 
    581     kw['arguments'] = arguments
    582 
    583     stderr = kw.get('stderr', None)
    584     if stderr:
    585       stderrs = stderr.split('$$$')
    586       kw['stderr'] = stderrs[1] if len(stderrs) > 1 else ''
    587 
    588     return self.run(program=self.build_tool, **kw)
    589 
    590   def build(self, gyp_file, target=None, status=0, **kw):
    591     # Two tools must be run to build, cmake and the ninja.
    592     # Allow cmake to succeed when the overall expectation is to fail.
    593     if status is None:
    594       kw['status'] = None
    595     else:
    596       if not isinstance(status, collections.Iterable): status = (status,)
    597       kw['status'] = list(itertools.chain((0,), status))
    598     self.cmake_build(gyp_file, target, **kw)
    599     kw['status'] = status
    600     self.ninja_build(gyp_file, target, **kw)
    601 
    602   def run_built_executable(self, name, *args, **kw):
    603     # Enclosing the name in a list avoids prepending the original dir.
    604     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
    605     if sys.platform == 'darwin':
    606       configuration = self.configuration_dirname()
    607       os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
    608     return self.run(program=program, *args, **kw)
    609 
    610   def built_file_path(self, name, type=None, **kw):
    611     result = []
    612     chdir = kw.get('chdir')
    613     if chdir:
    614       result.append(chdir)
    615     result.append('out')
    616     result.append(self.configuration_dirname())
    617     if type == self.STATIC_LIB:
    618       if sys.platform != 'darwin':
    619         result.append('obj.target')
    620     elif type == self.SHARED_LIB:
    621       if sys.platform != 'darwin' and sys.platform != 'win32':
    622         result.append('lib.target')
    623     subdir = kw.get('subdir')
    624     if subdir and type != self.SHARED_LIB:
    625       result.append(subdir)
    626     result.append(self.built_file_basename(name, type, **kw))
    627     return self.workpath(*result)
    628 
    629   def up_to_date(self, gyp_file, target=None, **kw):
    630     result = self.ninja_build(gyp_file, target, **kw)
    631     if not result:
    632       stdout = self.stdout()
    633       if 'ninja: no work to do' not in stdout:
    634         self.report_not_up_to_date()
    635         self.fail_test()
    636     return result
    637 
    638 
    639 class TestGypMake(TestGypBase):
    640   """
    641   Subclass for testing the GYP Make generator.
    642   """
    643   format = 'make'
    644   build_tool_list = ['make']
    645   ALL = 'all'
    646   def build(self, gyp_file, target=None, **kw):
    647     """
    648     Runs a Make build using the Makefiles generated from the specified
    649     gyp_file.
    650     """
    651     arguments = kw.get('arguments', [])[:]
    652     if self.configuration:
    653       arguments.append('BUILDTYPE=' + self.configuration)
    654     if target not in (None, self.DEFAULT):
    655       arguments.append(target)
    656     # Sub-directory builds provide per-gyp Makefiles (i.e.
    657     # Makefile.gyp_filename), so use that if there is no Makefile.
    658     chdir = kw.get('chdir', '')
    659     if not os.path.exists(os.path.join(chdir, 'Makefile')):
    660       print "NO Makefile in " + os.path.join(chdir, 'Makefile')
    661       arguments.insert(0, '-f')
    662       arguments.insert(1, os.path.splitext(gyp_file)[0] + '.Makefile')
    663     kw['arguments'] = arguments
    664     return self.run(program=self.build_tool, **kw)
    665   def up_to_date(self, gyp_file, target=None, **kw):
    666     """
    667     Verifies that a build of the specified Make target is up to date.
    668     """
    669     if target in (None, self.DEFAULT):
    670       message_target = 'all'
    671     else:
    672       message_target = target
    673     kw['stdout'] = "make: Nothing to be done for `%s'.\n" % message_target
    674     return self.build(gyp_file, target, **kw)
    675   def run_built_executable(self, name, *args, **kw):
    676     """
    677     Runs an executable built by Make.
    678     """
    679     configuration = self.configuration_dirname()
    680     libdir = os.path.join('out', configuration, 'lib')
    681     # TODO(piman): when everything is cross-compile safe, remove lib.target
    682     if sys.platform == 'darwin':
    683       # Mac puts target shared libraries right in the product directory.
    684       configuration = self.configuration_dirname()
    685       os.environ['DYLD_LIBRARY_PATH'] = (
    686           libdir + '.host:' + os.path.join('out', configuration))
    687     else:
    688       os.environ['LD_LIBRARY_PATH'] = libdir + '.host:' + libdir + '.target'
    689     # Enclosing the name in a list avoids prepending the original dir.
    690     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
    691     return self.run(program=program, *args, **kw)
    692   def built_file_path(self, name, type=None, **kw):
    693     """
    694     Returns a path to the specified file name, of the specified type,
    695     as built by Make.
    696 
    697     Built files are in the subdirectory 'out/{configuration}'.
    698     The default is 'out/Default'.
    699 
    700     A chdir= keyword argument specifies the source directory
    701     relative to which  the output subdirectory can be found.
    702 
    703     "type" values of STATIC_LIB or SHARED_LIB append the necessary
    704     prefixes and suffixes to a platform-independent library base name.
    705 
    706     A subdir= keyword argument specifies a library subdirectory within
    707     the default 'obj.target'.
    708     """
    709     result = []
    710     chdir = kw.get('chdir')
    711     if chdir:
    712       result.append(chdir)
    713     configuration = self.configuration_dirname()
    714     result.extend(['out', configuration])
    715     if type == self.STATIC_LIB and sys.platform != 'darwin':
    716       result.append('obj.target')
    717     elif type == self.SHARED_LIB and sys.platform != 'darwin':
    718       result.append('lib.target')
    719     subdir = kw.get('subdir')
    720     if subdir and type != self.SHARED_LIB:
    721       result.append(subdir)
    722     result.append(self.built_file_basename(name, type, **kw))
    723     return self.workpath(*result)
    724 
    725 
    726 def ConvertToCygpath(path):
    727   """Convert to cygwin path if we are using cygwin."""
    728   if sys.platform == 'cygwin':
    729     p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE)
    730     path = p.communicate()[0].strip()
    731   return path
    732 
    733 
    734 def FindVisualStudioInstallation():
    735   """Returns appropriate values for .build_tool and .uses_msbuild fields
    736   of TestGypBase for Visual Studio.
    737 
    738   We use the value specified by GYP_MSVS_VERSION.  If not specified, we
    739   search %PATH% and %PATHEXT% for a devenv.{exe,bat,...} executable.
    740   Failing that, we search for likely deployment paths.
    741   """
    742   possible_roots = ['%s:\\Program Files%s' % (chr(drive), suffix)
    743                     for drive in range(ord('C'), ord('Z') + 1)
    744                     for suffix in ['', ' (x86)']]
    745   possible_paths = {
    746       '2013': r'Microsoft Visual Studio 12.0\Common7\IDE\devenv.com',
    747       '2012': r'Microsoft Visual Studio 11.0\Common7\IDE\devenv.com',
    748       '2010': r'Microsoft Visual Studio 10.0\Common7\IDE\devenv.com',
    749       '2008': r'Microsoft Visual Studio 9.0\Common7\IDE\devenv.com',
    750       '2005': r'Microsoft Visual Studio 8\Common7\IDE\devenv.com'}
    751 
    752   possible_roots = [ConvertToCygpath(r) for r in possible_roots]
    753 
    754   msvs_version = 'auto'
    755   for flag in (f for f in sys.argv if f.startswith('msvs_version=')):
    756     msvs_version = flag.split('=')[-1]
    757   msvs_version = os.environ.get('GYP_MSVS_VERSION', msvs_version)
    758 
    759   build_tool = None
    760   if msvs_version in possible_paths:
    761     # Check that the path to the specified GYP_MSVS_VERSION exists.
    762     path = possible_paths[msvs_version]
    763     for r in possible_roots:
    764       bt = os.path.join(r, path)
    765       if os.path.exists(bt):
    766         build_tool = bt
    767         uses_msbuild = msvs_version >= '2010'
    768         return build_tool, uses_msbuild
    769     else:
    770       print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
    771               'but corresponding "%s" was not found.' % (msvs_version, path))
    772   if build_tool:
    773     # We found 'devenv' on the path, use that and try to guess the version.
    774     for version, path in possible_paths.iteritems():
    775       if build_tool.find(path) >= 0:
    776         uses_msbuild = version >= '2010'
    777         return build_tool, uses_msbuild
    778     else:
    779       # If not, assume not MSBuild.
    780       uses_msbuild = False
    781     return build_tool, uses_msbuild
    782   # Neither GYP_MSVS_VERSION nor the path help us out.  Iterate through
    783   # the choices looking for a match.
    784   for version in sorted(possible_paths, reverse=True):
    785     path = possible_paths[version]
    786     for r in possible_roots:
    787       bt = os.path.join(r, path)
    788       if os.path.exists(bt):
    789         build_tool = bt
    790         uses_msbuild = msvs_version >= '2010'
    791         return build_tool, uses_msbuild
    792   print 'Error: could not find devenv'
    793   sys.exit(1)
    794 
    795 class TestGypOnMSToolchain(TestGypBase):
    796   """
    797   Common subclass for testing generators that target the Microsoft Visual
    798   Studio toolchain (cl, link, dumpbin, etc.)
    799   """
    800   @staticmethod
    801   def _ComputeVsvarsPath(devenv_path):
    802     devenv_dir = os.path.split(devenv_path)[0]
    803     vsvars_path = os.path.join(devenv_path, '../../Tools/vsvars32.bat')
    804     return vsvars_path
    805 
    806   def initialize_build_tool(self):
    807     super(TestGypOnMSToolchain, self).initialize_build_tool()
    808     if sys.platform in ('win32', 'cygwin'):
    809       self.devenv_path, self.uses_msbuild = FindVisualStudioInstallation()
    810       self.vsvars_path = TestGypOnMSToolchain._ComputeVsvarsPath(
    811           self.devenv_path)
    812 
    813   def run_dumpbin(self, *dumpbin_args):
    814     """Run the dumpbin tool with the specified arguments, and capturing and
    815     returning stdout."""
    816     assert sys.platform in ('win32', 'cygwin')
    817     cmd = os.environ.get('COMSPEC', 'cmd.exe')
    818     arguments = [cmd, '/c', self.vsvars_path, '&&', 'dumpbin']
    819     arguments.extend(dumpbin_args)
    820     proc = subprocess.Popen(arguments, stdout=subprocess.PIPE)
    821     output = proc.communicate()[0]
    822     assert not proc.returncode
    823     return output
    824 
    825 class TestGypNinja(TestGypOnMSToolchain):
    826   """
    827   Subclass for testing the GYP Ninja generator.
    828   """
    829   format = 'ninja'
    830   build_tool_list = ['ninja']
    831   ALL = 'all'
    832   DEFAULT = 'all'
    833 
    834   def run_gyp(self, gyp_file, *args, **kw):
    835     TestGypBase.run_gyp(self, gyp_file, *args, **kw)
    836 
    837   def build(self, gyp_file, target=None, **kw):
    838     arguments = kw.get('arguments', [])[:]
    839 
    840     # Add a -C output/path to the command line.
    841     arguments.append('-C')
    842     arguments.append(os.path.join('out', self.configuration_dirname()))
    843 
    844     if target is None:
    845       target = 'all'
    846     arguments.append(target)
    847 
    848     kw['arguments'] = arguments
    849     return self.run(program=self.build_tool, **kw)
    850 
    851   def run_built_executable(self, name, *args, **kw):
    852     # Enclosing the name in a list avoids prepending the original dir.
    853     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
    854     if sys.platform == 'darwin':
    855       configuration = self.configuration_dirname()
    856       os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
    857     return self.run(program=program, *args, **kw)
    858 
    859   def built_file_path(self, name, type=None, **kw):
    860     result = []
    861     chdir = kw.get('chdir')
    862     if chdir:
    863       result.append(chdir)
    864     result.append('out')
    865     result.append(self.configuration_dirname())
    866     if type == self.STATIC_LIB:
    867       if sys.platform != 'darwin':
    868         result.append('obj')
    869     elif type == self.SHARED_LIB:
    870       if sys.platform != 'darwin' and sys.platform != 'win32':
    871         result.append('lib')
    872     subdir = kw.get('subdir')
    873     if subdir and type != self.SHARED_LIB:
    874       result.append(subdir)
    875     result.append(self.built_file_basename(name, type, **kw))
    876     return self.workpath(*result)
    877 
    878   def up_to_date(self, gyp_file, target=None, **kw):
    879     result = self.build(gyp_file, target, **kw)
    880     if not result:
    881       stdout = self.stdout()
    882       if 'ninja: no work to do' not in stdout:
    883         self.report_not_up_to_date()
    884         self.fail_test()
    885     return result
    886 
    887 
    888 class TestGypMSVS(TestGypOnMSToolchain):
    889   """
    890   Subclass for testing the GYP Visual Studio generator.
    891   """
    892   format = 'msvs'
    893 
    894   u = r'=== Build: 0 succeeded, 0 failed, (\d+) up-to-date, 0 skipped ==='
    895   up_to_date_re = re.compile(u, re.M)
    896 
    897   # Initial None element will indicate to our .initialize_build_tool()
    898   # method below that 'devenv' was not found on %PATH%.
    899   #
    900   # Note:  we must use devenv.com to be able to capture build output.
    901   # Directly executing devenv.exe only sends output to BuildLog.htm.
    902   build_tool_list = [None, 'devenv.com']
    903 
    904   def initialize_build_tool(self):
    905     super(TestGypMSVS, self).initialize_build_tool()
    906     self.build_tool = self.devenv_path
    907 
    908   def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
    909     """
    910     Runs a Visual Studio build using the configuration generated
    911     from the specified gyp_file.
    912     """
    913     configuration = self.configuration_buildname()
    914     if clean:
    915       build = '/Clean'
    916     elif rebuild:
    917       build = '/Rebuild'
    918     else:
    919       build = '/Build'
    920     arguments = kw.get('arguments', [])[:]
    921     arguments.extend([gyp_file.replace('.gyp', '.sln'),
    922                       build, configuration])
    923     # Note:  the Visual Studio generator doesn't add an explicit 'all'
    924     # target, so we just treat it the same as the default.
    925     if target not in (None, self.ALL, self.DEFAULT):
    926       arguments.extend(['/Project', target])
    927     if self.configuration:
    928       arguments.extend(['/ProjectConfig', self.configuration])
    929     kw['arguments'] = arguments
    930     return self.run(program=self.build_tool, **kw)
    931   def up_to_date(self, gyp_file, target=None, **kw):
    932     """
    933     Verifies that a build of the specified Visual Studio target is up to date.
    934 
    935     Beware that VS2010 will behave strangely if you build under
    936     C:\USERS\yourname\AppData\Local. It will cause needless work.  The ouptut
    937     will be "1 succeeded and 0 up to date".  MSBuild tracing reveals that:
    938     "Project 'C:\Users\...\AppData\Local\...vcxproj' not up to date because
    939     'C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 10.0\VC\BIN\1033\CLUI.DLL'
    940     was modified at 02/21/2011 17:03:30, which is newer than '' which was
    941     modified at 01/01/0001 00:00:00.
    942 
    943     The workaround is to specify a workdir when instantiating the test, e.g.
    944     test = TestGyp.TestGyp(workdir='workarea')
    945     """
    946     result = self.build(gyp_file, target, **kw)
    947     if not result:
    948       stdout = self.stdout()
    949 
    950       m = self.up_to_date_re.search(stdout)
    951       up_to_date = m and int(m.group(1)) > 0
    952       if not up_to_date:
    953         self.report_not_up_to_date()
    954         self.fail_test()
    955     return result
    956   def run_built_executable(self, name, *args, **kw):
    957     """
    958     Runs an executable built by Visual Studio.
    959     """
    960     configuration = self.configuration_dirname()
    961     # Enclosing the name in a list avoids prepending the original dir.
    962     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
    963     return self.run(program=program, *args, **kw)
    964   def built_file_path(self, name, type=None, **kw):
    965     """
    966     Returns a path to the specified file name, of the specified type,
    967     as built by Visual Studio.
    968 
    969     Built files are in a subdirectory that matches the configuration
    970     name.  The default is 'Default'.
    971 
    972     A chdir= keyword argument specifies the source directory
    973     relative to which  the output subdirectory can be found.
    974 
    975     "type" values of STATIC_LIB or SHARED_LIB append the necessary
    976     prefixes and suffixes to a platform-independent library base name.
    977     """
    978     result = []
    979     chdir = kw.get('chdir')
    980     if chdir:
    981       result.append(chdir)
    982     result.append(self.configuration_dirname())
    983     if type == self.STATIC_LIB:
    984       result.append('lib')
    985     result.append(self.built_file_basename(name, type, **kw))
    986     return self.workpath(*result)
    987 
    988 
    989 class TestGypXcode(TestGypBase):
    990   """
    991   Subclass for testing the GYP Xcode generator.
    992   """
    993   format = 'xcode'
    994   build_tool_list = ['xcodebuild']
    995 
    996   phase_script_execution = ("\n"
    997                             "PhaseScriptExecution /\\S+/Script-[0-9A-F]+\\.sh\n"
    998                             "    cd /\\S+\n"
    999                             "    /bin/sh -c /\\S+/Script-[0-9A-F]+\\.sh\n"
   1000                             "(make: Nothing to be done for `all'\\.\n)?")
   1001 
   1002   strip_up_to_date_expressions = [
   1003     # Various actions or rules can run even when the overall build target
   1004     # is up to date.  Strip those phases' GYP-generated output.
   1005     re.compile(phase_script_execution, re.S),
   1006 
   1007     # The message from distcc_pump can trail the "BUILD SUCCEEDED"
   1008     # message, so strip that, too.
   1009     re.compile('__________Shutting down distcc-pump include server\n', re.S),
   1010   ]
   1011 
   1012   up_to_date_endings = (
   1013     'Checking Dependencies...\n** BUILD SUCCEEDED **\n', # Xcode 3.0/3.1
   1014     'Check dependencies\n** BUILD SUCCEEDED **\n\n',     # Xcode 3.2
   1015     'Check dependencies\n\n\n** BUILD SUCCEEDED **\n\n', # Xcode 4.2
   1016     'Check dependencies\n\n** BUILD SUCCEEDED **\n\n',   # Xcode 5.0
   1017   )
   1018 
   1019   def build(self, gyp_file, target=None, **kw):
   1020     """
   1021     Runs an xcodebuild using the .xcodeproj generated from the specified
   1022     gyp_file.
   1023     """
   1024     # Be sure we're working with a copy of 'arguments' since we modify it.
   1025     # The caller may not be expecting it to be modified.
   1026     arguments = kw.get('arguments', [])[:]
   1027     arguments.extend(['-project', gyp_file.replace('.gyp', '.xcodeproj')])
   1028     if target == self.ALL:
   1029       arguments.append('-alltargets',)
   1030     elif target not in (None, self.DEFAULT):
   1031       arguments.extend(['-target', target])
   1032     if self.configuration:
   1033       arguments.extend(['-configuration', self.configuration])
   1034     symroot = kw.get('SYMROOT', '$SRCROOT/build')
   1035     if symroot:
   1036       arguments.append('SYMROOT='+symroot)
   1037     kw['arguments'] = arguments
   1038 
   1039     # Work around spurious stderr output from Xcode 4, http://crbug.com/181012
   1040     match = kw.pop('match', self.match)
   1041     def match_filter_xcode(actual, expected):
   1042       if actual:
   1043         if not TestCmd.is_List(actual):
   1044           actual = actual.split('\n')
   1045         if not TestCmd.is_List(expected):
   1046           expected = expected.split('\n')
   1047         actual = [a for a in actual
   1048                     if 'No recorder, buildTask: <Xcode3BuildTask:' not in a]
   1049       return match(actual, expected)
   1050     kw['match'] = match_filter_xcode
   1051 
   1052     return self.run(program=self.build_tool, **kw)
   1053   def up_to_date(self, gyp_file, target=None, **kw):
   1054     """
   1055     Verifies that a build of the specified Xcode target is up to date.
   1056     """
   1057     result = self.build(gyp_file, target, **kw)
   1058     if not result:
   1059       output = self.stdout()
   1060       for expression in self.strip_up_to_date_expressions:
   1061         output = expression.sub('', output)
   1062       if not output.endswith(self.up_to_date_endings):
   1063         self.report_not_up_to_date()
   1064         self.fail_test()
   1065     return result
   1066   def run_built_executable(self, name, *args, **kw):
   1067     """
   1068     Runs an executable built by xcodebuild.
   1069     """
   1070     configuration = self.configuration_dirname()
   1071     os.environ['DYLD_LIBRARY_PATH'] = os.path.join('build', configuration)
   1072     # Enclosing the name in a list avoids prepending the original dir.
   1073     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
   1074     return self.run(program=program, *args, **kw)
   1075   def built_file_path(self, name, type=None, **kw):
   1076     """
   1077     Returns a path to the specified file name, of the specified type,
   1078     as built by Xcode.
   1079 
   1080     Built files are in the subdirectory 'build/{configuration}'.
   1081     The default is 'build/Default'.
   1082 
   1083     A chdir= keyword argument specifies the source directory
   1084     relative to which  the output subdirectory can be found.
   1085 
   1086     "type" values of STATIC_LIB or SHARED_LIB append the necessary
   1087     prefixes and suffixes to a platform-independent library base name.
   1088     """
   1089     result = []
   1090     chdir = kw.get('chdir')
   1091     if chdir:
   1092       result.append(chdir)
   1093     configuration = self.configuration_dirname()
   1094     result.extend(['build', configuration])
   1095     result.append(self.built_file_basename(name, type, **kw))
   1096     return self.workpath(*result)
   1097 
   1098 
   1099 format_class_list = [
   1100   TestGypGypd,
   1101   TestGypAndroid,
   1102   TestGypCMake,
   1103   TestGypMake,
   1104   TestGypMSVS,
   1105   TestGypNinja,
   1106   TestGypXcode,
   1107 ]
   1108 
   1109 def TestGyp(*args, **kw):
   1110   """
   1111   Returns an appropriate TestGyp* instance for a specified GYP format.
   1112   """
   1113   format = kw.pop('format', os.environ.get('TESTGYP_FORMAT'))
   1114   for format_class in format_class_list:
   1115     if format == format_class.format:
   1116       return format_class(*args, **kw)
   1117   raise Exception, "unknown format %r" % format
   1118