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""" 8TestGyp.py: a testing framework for GYP integration tests. 9""" 10 11import os 12import re 13import shutil 14import stat 15import sys 16 17import TestCommon 18from TestCommon import __all__ 19 20__all__.extend([ 21 'TestGyp', 22]) 23 24 25class 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 308class 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 316class 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 394class 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 538class 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 610class 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 704format_class_list = [ 705 TestGypGypd, 706 TestGypMake, 707 TestGypMSVS, 708 TestGypSCons, 709 TestGypXcode, 710] 711 712def 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 725