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