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