initial import
[vuplus_webkit] / Source / ThirdParty / gyp / test / lib / TestGyp.py
1 #!/usr/bin/python
2
3 # Copyright (c) 2009 Google Inc. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """
8 TestGyp.py:  a testing framework for GYP integration tests.
9 """
10
11 import os
12 import re
13 import shutil
14 import stat
15 import sys
16
17 import TestCommon
18 from TestCommon import __all__
19
20 __all__.extend([
21   'TestGyp',
22 ])
23
24
25 class TestGypBase(TestCommon.TestCommon):
26   """
27   Class for controlling end-to-end tests of gyp generators.
28
29   Instantiating this class will create a temporary directory and
30   arrange for its destruction (via the TestCmd superclass) and
31   copy all of the non-gyptest files in the directory hierarchy of the
32   executing script.
33
34   The default behavior is to test the 'gyp' or 'gyp.bat' file in the
35   current directory.  An alternative may be specified explicitly on
36   instantiation, or by setting the TESTGYP_GYP environment variable.
37
38   This class should be subclassed for each supported gyp generator
39   (format).  Various abstract methods below define calling signatures
40   used by the test scripts to invoke builds on the generated build
41   configuration and to run executables generated by those builds.
42   """
43
44   build_tool = None
45   build_tool_list = []
46
47   _exe = TestCommon.exe_suffix
48   _obj = TestCommon.obj_suffix
49   shobj_ = TestCommon.shobj_prefix
50   _shobj = TestCommon.shobj_suffix
51   lib_ = TestCommon.lib_prefix
52   _lib = TestCommon.lib_suffix
53   dll_ = TestCommon.dll_prefix
54   _dll = TestCommon.dll_suffix
55
56   # Constants to represent different targets.
57   ALL = '__all__'
58   DEFAULT = '__default__'
59
60   # Constants for different target types.
61   EXECUTABLE = '__executable__'
62   STATIC_LIB = '__static_lib__'
63   SHARED_LIB = '__shared_lib__'
64
65   def __init__(self, gyp=None, *args, **kw):
66     self.origin_cwd = os.path.abspath(os.path.dirname(sys.argv[0]))
67
68     if not gyp:
69       gyp = os.environ.get('TESTGYP_GYP')
70       if not gyp:
71         if sys.platform == 'win32':
72           gyp = 'gyp.bat'
73         else:
74           gyp = 'gyp'
75     self.gyp = os.path.abspath(gyp)
76
77     self.initialize_build_tool()
78
79     if not kw.has_key('match'):
80       kw['match'] = TestCommon.match_exact
81
82     if not kw.has_key('workdir'):
83       # Default behavior:  the null string causes TestCmd to create
84       # a temporary directory for us.
85       kw['workdir'] = ''
86
87     formats = kw.get('formats', [])
88     if kw.has_key('formats'):
89       del kw['formats']
90
91     super(TestGypBase, self).__init__(*args, **kw)
92
93     excluded_formats = set([f for f in formats if f[0] == '!'])
94     included_formats = set(formats) - excluded_formats
95     if ('!'+self.format in excluded_formats or
96         included_formats and self.format not in included_formats):
97       msg = 'Invalid test for %r format; skipping test.\n'
98       self.skip_test(msg % self.format)
99
100     self.copy_test_configuration(self.origin_cwd, self.workdir)
101     self.set_configuration(None)
102
103   def built_file_must_exist(self, name, type=None, **kw):
104     """
105     Fails the test if the specified built file name does not exist.
106     """
107     return self.must_exist(self.built_file_path(name, type, **kw))
108
109   def built_file_must_not_exist(self, name, type=None, **kw):
110     """
111     Fails the test if the specified built file name exists.
112     """
113     return self.must_not_exist(self.built_file_path(name, type, **kw))
114
115   def built_file_must_match(self, name, contents, **kw):
116     """
117     Fails the test if the contents of the specified built file name
118     do not match the specified contents.
119     """
120     return self.must_match(self.built_file_path(name, **kw), contents)
121
122   def built_file_must_not_match(self, name, contents, **kw):
123     """
124     Fails the test if the contents of the specified built file name
125     match the specified contents.
126     """
127     return self.must_not_match(self.built_file_path(name, **kw), contents)
128
129   def copy_test_configuration(self, source_dir, dest_dir):
130     """
131     Copies the test configuration from the specified source_dir
132     (the directory in which the test script lives) to the
133     specified dest_dir (a temporary working directory).
134
135     This ignores all files and directories that begin with
136     the string 'gyptest', and all '.svn' subdirectories.
137     """
138     for root, dirs, files in os.walk(source_dir):
139       if '.svn' in dirs:
140         dirs.remove('.svn')
141       dirs = [ d for d in dirs if not d.startswith('gyptest') ]
142       files = [ f for f in files if not f.startswith('gyptest') ]
143       for dirname in dirs:
144         source = os.path.join(root, dirname)
145         destination = source.replace(source_dir, dest_dir)
146         os.mkdir(destination)
147         if sys.platform != 'win32':
148           shutil.copystat(source, destination)
149       for filename in files:
150         source = os.path.join(root, filename)
151         destination = source.replace(source_dir, dest_dir)
152         shutil.copy2(source, destination)
153
154   def initialize_build_tool(self):
155     """
156     Initializes the .build_tool attribute.
157
158     Searches the .build_tool_list for an executable name on the user's
159     $PATH.  The first tool on the list is used as-is if nothing is found
160     on the current $PATH.
161     """
162     for build_tool in self.build_tool_list:
163       if not build_tool:
164         continue
165       if os.path.isabs(build_tool):
166         self.build_tool = build_tool
167         return
168       build_tool = self.where_is(build_tool)
169       if build_tool:
170         self.build_tool = build_tool
171         return
172
173     if self.build_tool_list:
174       self.build_tool = self.build_tool_list[0]
175
176   def relocate(self, source, destination):
177     """
178     Renames (relocates) the specified source (usually a directory)
179     to the specified destination, creating the destination directory
180     first if necessary.
181
182     Note:  Don't use this as a generic "rename" operation.  In the
183     future, "relocating" parts of a GYP tree may affect the state of
184     the test to modify the behavior of later method calls.
185     """
186     destination_dir = os.path.dirname(destination)
187     if not os.path.exists(destination_dir):
188       self.subdir(destination_dir)
189     os.rename(source, destination)
190
191   def report_not_up_to_date(self):
192     """
193     Reports that a build is not up-to-date.
194
195     This provides common reporting for formats that have complicated
196     conditions for checking whether a build is up-to-date.  Formats
197     that expect exact output from the command (make, scons) can
198     just set stdout= when they call the run_build() method.
199     """
200     print "Build is not up-to-date:"
201     print self.banner('STDOUT ')
202     print self.stdout()
203     stderr = self.stderr()
204     if stderr:
205       print self.banner('STDERR ')
206       print stderr
207
208   def run_gyp(self, gyp_file, *args, **kw):
209     """
210     Runs gyp against the specified gyp_file with the specified args.
211     """
212     # TODO:  --depth=. works around Chromium-specific tree climbing.
213     args = ('--depth=.', '--format='+self.format, gyp_file) + args
214     return self.run(program=self.gyp, arguments=args, **kw)
215
216   def run(self, *args, **kw):
217     """
218     Executes a program by calling the superclass .run() method.
219
220     This exists to provide a common place to filter out keyword
221     arguments implemented in this layer, without having to update
222     the tool-specific subclasses or clutter the tests themselves
223     with platform-specific code.
224     """
225     if kw.has_key('SYMROOT'):
226       del kw['SYMROOT']
227     super(TestGypBase, self).run(*args, **kw)
228
229   def set_configuration(self, configuration):
230     """
231     Sets the configuration, to be used for invoking the build
232     tool and testing potential built output.
233     """
234     self.configuration = configuration
235
236   def configuration_dirname(self):
237     if self.configuration:
238       return self.configuration.split('|')[0]
239     else:
240       return 'Default'
241
242   def configuration_buildname(self):
243     if self.configuration:
244       return self.configuration
245     else:
246       return 'Default'
247
248   #
249   # Abstract methods to be defined by format-specific subclasses.
250   #
251
252   def build(self, gyp_file, target=None, **kw):
253     """
254     Runs a build of the specified target against the configuration
255     generated from the specified gyp_file.
256
257     A 'target' argument of None or the special value TestGyp.DEFAULT
258     specifies the default argument for the underlying build tool.
259     A 'target' argument of TestGyp.ALL specifies the 'all' target
260     (if any) of the underlying build tool.
261     """
262     raise NotImplementedError
263
264   def built_file_path(self, name, type=None, **kw):
265     """
266     Returns a path to the specified file name, of the specified type.
267     """
268     raise NotImplementedError
269
270   def built_file_basename(self, name, type=None, **kw):
271     """
272     Returns the base name of the specified file name, of the specified type.
273
274     A bare=True keyword argument specifies that prefixes and suffixes shouldn't
275     be applied.
276     """
277     if not kw.get('bare'):
278       if type == self.EXECUTABLE:
279         name = name + self._exe
280       elif type == self.STATIC_LIB:
281         name = self.lib_ + name + self._lib
282       elif type == self.SHARED_LIB:
283         name = self.dll_ + name + self._dll
284     return name
285
286   def run_built_executable(self, name, *args, **kw):
287     """
288     Runs an executable program built from a gyp-generated configuration.
289
290     The specified name should be independent of any particular generator.
291     Subclasses should find the output executable in the appropriate
292     output build directory, tack on any necessary executable suffix, etc.
293     """
294     raise NotImplementedError
295
296   def up_to_date(self, gyp_file, target=None, **kw):
297     """
298     Verifies that a build of the specified target is up to date.
299
300     The subclass should implement this by calling build()
301     (or a reasonable equivalent), checking whatever conditions
302     will tell it the build was an "up to date" null build, and
303     failing if it isn't.
304     """
305     raise NotImplementedError
306
307
308 class TestGypGypd(TestGypBase):
309   """
310   Subclass for testing the GYP 'gypd' generator (spit out the
311   internal data structure as pretty-printed Python).
312   """
313   format = 'gypd'
314
315
316 class TestGypMake(TestGypBase):
317   """
318   Subclass for testing the GYP Make generator.
319   """
320   format = 'make'
321   build_tool_list = ['make']
322   ALL = 'all'
323   def build(self, gyp_file, target=None, **kw):
324     """
325     Runs a Make build using the Makefiles generated from the specified
326     gyp_file.
327     """
328     arguments = kw.get('arguments', [])[:]
329     if self.configuration:
330       arguments.append('BUILDTYPE=' + self.configuration)
331     if target not in (None, self.DEFAULT):
332       arguments.append(target)
333     # Sub-directory builds provide per-gyp Makefiles (i.e.
334     # Makefile.gyp_filename), so use that if there is no Makefile.
335     chdir = kw.get('chdir', '')
336     if not os.path.exists(os.path.join(chdir, 'Makefile')):
337       print "NO Makefile in " + os.path.join(chdir, 'Makefile')
338       arguments.insert(0, '-f')
339       arguments.insert(1, os.path.splitext(gyp_file)[0] + '.Makefile')
340     kw['arguments'] = arguments
341     return self.run(program=self.build_tool, **kw)
342   def up_to_date(self, gyp_file, target=None, **kw):
343     """
344     Verifies that a build of the specified Make target is up to date.
345     """
346     if target in (None, self.DEFAULT):
347       message_target = 'all'
348     else:
349       message_target = target
350     kw['stdout'] = "make: Nothing to be done for `%s'.\n" % message_target
351     return self.build(gyp_file, target, **kw)
352   def run_built_executable(self, name, *args, **kw):
353     """
354     Runs an executable built by Make.
355     """
356     configuration = self.configuration_dirname()
357     libdir = os.path.join('out', configuration, 'lib')
358     # TODO(piman): when everything is cross-compile safe, remove lib.target
359     os.environ['LD_LIBRARY_PATH'] = libdir + '.host:' + libdir + '.target'
360     # Enclosing the name in a list avoids prepending the original dir.
361     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
362     return self.run(program=program, *args, **kw)
363   def built_file_path(self, name, type=None, **kw):
364     """
365     Returns a path to the specified file name, of the specified type,
366     as built by Make.
367
368     Built files are in the subdirectory 'out/{configuration}'.
369     The default is 'out/Default'.
370
371     A chdir= keyword argument specifies the source directory
372     relative to which  the output subdirectory can be found.
373
374     "type" values of STATIC_LIB or SHARED_LIB append the necessary
375     prefixes and suffixes to a platform-independent library base name.
376
377     A libdir= keyword argument specifies a library subdirectory other
378     than the default 'obj.target'.
379     """
380     result = []
381     chdir = kw.get('chdir')
382     if chdir:
383       result.append(chdir)
384     configuration = self.configuration_dirname()
385     result.extend(['out', configuration])
386     if type == self.STATIC_LIB:
387       result.append(kw.get('libdir', 'obj.target'))
388     elif type == self.SHARED_LIB:
389       result.append(kw.get('libdir', 'lib.target'))
390     result.append(self.built_file_basename(name, type, **kw))
391     return self.workpath(*result)
392
393
394 class TestGypMSVS(TestGypBase):
395   """
396   Subclass for testing the GYP Visual Studio generator.
397   """
398   format = 'msvs'
399
400   u = r'=== Build: (\d+) succeeded, 0 failed, (\d+) up-to-date, 0 skipped ==='
401   up_to_date_re = re.compile(u, re.M)
402
403   # Initial None element will indicate to our .initialize_build_tool()
404   # method below that 'devenv' was not found on %PATH%.
405   #
406   # Note:  we must use devenv.com to be able to capture build output.
407   # Directly executing devenv.exe only sends output to BuildLog.htm.
408   build_tool_list = [None, 'devenv.com']
409
410   def initialize_build_tool(self):
411     """ Initializes the Visual Studio .build_tool and .uses_msbuild parameters.
412
413     We use the value specified by GYP_MSVS_VERSION.  If not specified, we
414     search %PATH% and %PATHEXT% for a devenv.{exe,bat,...} executable.
415     Failing that, we search for likely deployment paths.
416     """
417     super(TestGypMSVS, self).initialize_build_tool()
418     possible_roots = ['C:\\Program Files (x86)', 'C:\\Program Files']
419     possible_paths = {
420         '2010': r'Microsoft Visual Studio 10.0\Common7\IDE\devenv.com',
421         '2008': r'Microsoft Visual Studio 9.0\Common7\IDE\devenv.com',
422         '2005': r'Microsoft Visual Studio 8\Common7\IDE\devenv.com'}
423     msvs_version = os.environ.get('GYP_MSVS_VERSION', 'auto')
424     if msvs_version in possible_paths:
425       # Check that the path to the specified GYP_MSVS_VERSION exists.
426       path = possible_paths[msvs_version]
427       for r in possible_roots:
428         bt = os.path.join(r, path)
429         if os.path.exists(bt):
430           self.build_tool = bt
431           self.uses_msbuild = msvs_version >= '2010'
432           return
433       else:
434         print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
435                'but corresponding "%s" was not found.' % (msvs_version, path))
436     if self.build_tool:
437       # We found 'devenv' on the path, use that and try to guess the version.
438       for version, path in possible_paths.iteritems():
439         if self.build_tool.find(path) >= 0:
440           self.uses_msbuild = version >= '2010'
441           return
442       else:
443         # If not, assume not MSBuild.
444         self.uses_msbuild = False
445       return
446     # Neither GYP_MSVS_VERSION nor the path help us out.  Iterate through
447     # the choices looking for a match.
448     for version, path in possible_paths.iteritems():
449       for r in possible_roots:
450         bt = os.path.join(r, path)
451         if os.path.exists(bt):
452           self.build_tool = bt
453           self.uses_msbuild = msvs_version >= '2010'
454           return
455     print 'Error: could not find devenv'
456     sys.exit(1)
457   def build(self, gyp_file, target=None, rebuild=False, **kw):
458     """
459     Runs a Visual Studio build using the configuration generated
460     from the specified gyp_file.
461     """
462     configuration = self.configuration_buildname()
463     if rebuild:
464       build = '/Rebuild'
465     else:
466       build = '/Build'
467     arguments = kw.get('arguments', [])[:]
468     arguments.extend([gyp_file.replace('.gyp', '.sln'),
469                       build, configuration])
470     # Note:  the Visual Studio generator doesn't add an explicit 'all'
471     # target, so we just treat it the same as the default.
472     if target not in (None, self.ALL, self.DEFAULT):
473       arguments.extend(['/Project', target])
474     if self.configuration:
475       arguments.extend(['/ProjectConfig', self.configuration])
476     kw['arguments'] = arguments
477     return self.run(program=self.build_tool, **kw)
478   def up_to_date(self, gyp_file, target=None, **kw):
479     """
480     Verifies that a build of the specified Visual Studio target is up to date.
481     """
482     result = self.build(gyp_file, target, **kw)
483     if not result:
484       stdout = self.stdout()
485       m = self.up_to_date_re.search(stdout)
486       up_to_date = False
487       if m:
488         succeeded = m.group(1)
489         up_to_date = m.group(2)
490         up_to_date = succeeded == '0' and up_to_date == '1'
491         # Figuring out if the build is up to date changed with VS2010.
492         # For builds that should be up to date, I sometimes get
493         # "1 succeeded and 0 up to date".  As an ad-hoc measure, we check
494         # this and also verify that th number of output lines is small.
495         # I don't know if this is caused by VS itself or is due to
496         # interaction with virus checkers.
497         if self.uses_msbuild and (succeeded == '1' and
498              up_to_date == '0' and
499              stdout.count('\n') <= 6):
500           up_to_date = True
501       if not up_to_date:
502         self.report_not_up_to_date()
503         self.fail_test()
504     return result
505   def run_built_executable(self, name, *args, **kw):
506     """
507     Runs an executable built by Visual Studio.
508     """
509     configuration = self.configuration_dirname()
510     # Enclosing the name in a list avoids prepending the original dir.
511     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
512     return self.run(program=program, *args, **kw)
513   def built_file_path(self, name, type=None, **kw):
514     """
515     Returns a path to the specified file name, of the specified type,
516     as built by Visual Studio.
517
518     Built files are in a subdirectory that matches the configuration
519     name.  The default is 'Default'.
520
521     A chdir= keyword argument specifies the source directory
522     relative to which  the output subdirectory can be found.
523
524     "type" values of STATIC_LIB or SHARED_LIB append the necessary
525     prefixes and suffixes to a platform-independent library base name.
526     """
527     result = []
528     chdir = kw.get('chdir')
529     if chdir:
530       result.append(chdir)
531     result.append(self.configuration_dirname())
532     if type == self.STATIC_LIB:
533       result.append('lib')
534     result.append(self.built_file_basename(name, type, **kw))
535     return self.workpath(*result)
536
537
538 class TestGypSCons(TestGypBase):
539   """
540   Subclass for testing the GYP SCons generator.
541   """
542   format = 'scons'
543   build_tool_list = ['scons', 'scons.py']
544   ALL = 'all'
545   def build(self, gyp_file, target=None, **kw):
546     """
547     Runs a scons build using the SCons configuration generated from the
548     specified gyp_file.
549     """
550     arguments = kw.get('arguments', [])[:]
551     dirname = os.path.dirname(gyp_file)
552     if dirname:
553       arguments.extend(['-C', dirname])
554     if self.configuration:
555       arguments.append('--mode=' + self.configuration)
556     if target not in (None, self.DEFAULT):
557       arguments.append(target)
558     kw['arguments'] = arguments
559     return self.run(program=self.build_tool, **kw)
560   def up_to_date(self, gyp_file, target=None, **kw):
561     """
562     Verifies that a build of the specified SCons target is up to date.
563     """
564     if target in (None, self.DEFAULT):
565       up_to_date_targets = 'all'
566     else:
567       up_to_date_targets = target
568     up_to_date_lines = []
569     for arg in up_to_date_targets.split():
570       up_to_date_lines.append("scons: `%s' is up to date.\n" % arg)
571     kw['stdout'] = ''.join(up_to_date_lines)
572     arguments = kw.get('arguments', [])[:]
573     arguments.append('-Q')
574     kw['arguments'] = arguments
575     return self.build(gyp_file, target, **kw)
576   def run_built_executable(self, name, *args, **kw):
577     """
578     Runs an executable built by scons.
579     """
580     configuration = self.configuration_dirname()
581     os.environ['LD_LIBRARY_PATH'] = os.path.join(configuration, 'lib')
582     # Enclosing the name in a list avoids prepending the original dir.
583     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
584     return self.run(program=program, *args, **kw)
585   def built_file_path(self, name, type=None, **kw):
586     """
587     Returns a path to the specified file name, of the specified type,
588     as built by Scons.
589
590     Built files are in a subdirectory that matches the configuration
591     name.  The default is 'Default'.
592
593     A chdir= keyword argument specifies the source directory
594     relative to which  the output subdirectory can be found.
595
596     "type" values of STATIC_LIB or SHARED_LIB append the necessary
597     prefixes and suffixes to a platform-independent library base name.
598     """
599     result = []
600     chdir = kw.get('chdir')
601     if chdir:
602       result.append(chdir)
603     result.append(self.configuration_dirname())
604     if type in (self.STATIC_LIB, self.SHARED_LIB):
605       result.append('lib')
606     result.append(self.built_file_basename(name, type, **kw))
607     return self.workpath(*result)
608
609
610 class TestGypXcode(TestGypBase):
611   """
612   Subclass for testing the GYP Xcode generator.
613   """
614   format = 'xcode'
615   build_tool_list = ['xcodebuild']
616
617   phase_script_execution = ("\n"
618                             "PhaseScriptExecution /\\S+/Script-[0-9A-F]+\\.sh\n"
619                             "    cd /\\S+\n"
620                             "    /bin/sh -c /\\S+/Script-[0-9A-F]+\\.sh\n"
621                             "(make: Nothing to be done for `all'\\.\n)?")
622
623   strip_up_to_date_expressions = [
624     # Various actions or rules can run even when the overall build target
625     # is up to date.  Strip those phases' GYP-generated output.
626     re.compile(phase_script_execution, re.S),
627
628     # The message from distcc_pump can trail the "BUILD SUCCEEDED"
629     # message, so strip that, too.
630     re.compile('__________Shutting down distcc-pump include server\n', re.S),
631   ]
632
633   up_to_date_endings = (
634     'Checking Dependencies...\n** BUILD SUCCEEDED **\n', # Xcode 3.0/3.1
635     'Check dependencies\n** BUILD SUCCEEDED **\n\n',     # Xcode 3.2
636   )
637
638   def build(self, gyp_file, target=None, **kw):
639     """
640     Runs an xcodebuild using the .xcodeproj generated from the specified
641     gyp_file.
642     """
643     # Be sure we're working with a copy of 'arguments' since we modify it.
644     # The caller may not be expecting it to be modified.
645     arguments = kw.get('arguments', [])[:]
646     arguments.extend(['-project', gyp_file.replace('.gyp', '.xcodeproj')])
647     if target == self.ALL:
648       arguments.append('-alltargets',)
649     elif target not in (None, self.DEFAULT):
650       arguments.extend(['-target', target])
651     if self.configuration:
652       arguments.extend(['-configuration', self.configuration])
653     symroot = kw.get('SYMROOT', '$SRCROOT/build')
654     if symroot:
655       arguments.append('SYMROOT='+symroot)
656     kw['arguments'] = arguments
657     return self.run(program=self.build_tool, **kw)
658   def up_to_date(self, gyp_file, target=None, **kw):
659     """
660     Verifies that a build of the specified Xcode target is up to date.
661     """
662     result = self.build(gyp_file, target, **kw)
663     if not result:
664       output = self.stdout()
665       for expression in self.strip_up_to_date_expressions:
666         output = expression.sub('', output)
667       if not output.endswith(self.up_to_date_endings):
668         self.report_not_up_to_date()
669         self.fail_test()
670     return result
671   def run_built_executable(self, name, *args, **kw):
672     """
673     Runs an executable built by xcodebuild.
674     """
675     configuration = self.configuration_dirname()
676     os.environ['DYLD_LIBRARY_PATH'] = os.path.join('build', configuration)
677     # Enclosing the name in a list avoids prepending the original dir.
678     program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
679     return self.run(program=program, *args, **kw)
680   def built_file_path(self, name, type=None, **kw):
681     """
682     Returns a path to the specified file name, of the specified type,
683     as built by Xcode.
684
685     Built files are in the subdirectory 'build/{configuration}'.
686     The default is 'build/Default'.
687
688     A chdir= keyword argument specifies the source directory
689     relative to which  the output subdirectory can be found.
690
691     "type" values of STATIC_LIB or SHARED_LIB append the necessary
692     prefixes and suffixes to a platform-independent library base name.
693     """
694     result = []
695     chdir = kw.get('chdir')
696     if chdir:
697       result.append(chdir)
698     configuration = self.configuration_dirname()
699     result.extend(['build', configuration])
700     result.append(self.built_file_basename(name, type, **kw))
701     return self.workpath(*result)
702
703
704 format_class_list = [
705   TestGypGypd,
706   TestGypMake,
707   TestGypMSVS,
708   TestGypSCons,
709   TestGypXcode,
710 ]
711
712 def TestGyp(*args, **kw):
713   """
714   Returns an appropriate TestGyp* instance for a specified GYP format.
715   """
716   format = kw.get('format')
717   if format:
718     del kw['format']
719   else:
720     format = os.environ.get('TESTGYP_FORMAT')
721   for format_class in format_class_list:
722     if format == format_class.format:
723       return format_class(*args, **kw)
724   raise Exception, "unknown format %r" % format