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