Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / gyp / test / lib / TestGyp.py
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 from contextlib import contextmanager
11 import itertools
12 import os
13 import random
14 import re
15 import shutil
16 import stat
17 import subprocess
18 import sys
19 import tempfile
20 import time
21
22 import TestCmd
23 import TestCommon
24 from TestCommon import __all__
25
26 __all__.extend([
27   'TestGyp',
28 ])
29
30
31 def 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
45 def 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
53 def 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
64 class 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
387 class 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
400 class 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
410 class 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
680 class 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
781 class 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
868 def 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
876 def 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
923 def 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
973 class 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
1004 class 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
1067 class 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
1168 class 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
1218 class 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
1328 format_class_list = [
1329   TestGypGypd,
1330   TestGypAndroid,
1331   TestGypCMake,
1332   TestGypMake,
1333   TestGypMSVS,
1334   TestGypMSVSNinja,
1335   TestGypNinja,
1336   TestGypXcode,
1337 ]
1338
1339 def 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