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.
6 TestGyp.py: a testing framework for GYP integration tests.
10 from contextlib import contextmanager
24 from TestCommon import __all__
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
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)
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)
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)
61 os.environ.update(old_env)
64 class TestGypBase(TestCommon.TestCommon):
66 Class for controlling end-to-end tests of gyp generators.
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
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.
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.
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
96 # Constants to represent different targets.
98 DEFAULT = '__default__'
100 # Constants for different target types.
101 EXECUTABLE = '__executable__'
102 STATIC_LIB = '__static_lib__'
103 SHARED_LIB = '__shared_lib__'
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:]
110 gyp = os.environ.get('TESTGYP_GYP')
112 if sys.platform == 'win32':
116 self.gyp = os.path.abspath(gyp)
117 self.no_parallel = False
119 self.formats = [self.format]
121 self.initialize_build_tool()
123 kw.setdefault('match', TestCommon.match_exact)
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):
132 kw['workdir'] = tempfile.mktemp(prefix='testgyp.', dir=workdir)
134 formats = kw.pop('formats', [])
136 super(TestGypBase, self).__init__(*args, **kw)
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)
146 self.copy_test_configuration(self.origin_cwd, self.workdir)
147 self.set_configuration(None)
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'
160 def built_file_must_exist(self, name, type=None, **kw):
162 Fails the test if the specified built file name does not exist.
164 return self.must_exist(self.built_file_path(name, type, **kw))
166 def built_file_must_not_exist(self, name, type=None, **kw):
168 Fails the test if the specified built file name exists.
170 return self.must_not_exist(self.built_file_path(name, type, **kw))
172 def built_file_must_match(self, name, contents, **kw):
174 Fails the test if the contents of the specified built file name
175 do not match the specified contents.
177 return self.must_match(self.built_file_path(name, **kw), contents)
179 def built_file_must_not_match(self, name, contents, **kw):
181 Fails the test if the contents of the specified built file name
182 match the specified contents.
184 return self.must_not_match(self.built_file_path(name, **kw), contents)
186 def built_file_must_not_contain(self, name, contents, **kw):
188 Fails the test if the specified built file name contains the specified
191 return self.must_not_contain(self.built_file_path(name, **kw), contents)
193 def copy_test_configuration(self, source_dir, dest_dir):
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).
199 This ignores all files and directories that begin with
200 the string 'gyptest', and all '.svn' subdirectories.
202 for root, dirs, files in os.walk(source_dir):
205 dirs = [ d for d in dirs if not d.startswith('gyptest') ]
206 files = [ f for f in files if not f.startswith('gyptest') ]
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)
218 def initialize_build_tool(self):
220 Initializes the .build_tool attribute.
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.
226 for build_tool in self.build_tool_list:
229 if os.path.isabs(build_tool):
230 self.build_tool = build_tool
232 build_tool = self.where_is(build_tool)
234 self.build_tool = build_tool
237 if self.build_tool_list:
238 self.build_tool = self.build_tool_list[0]
240 def relocate(self, source, destination):
242 Renames (relocates) the specified source (usually a directory)
243 to the specified destination, creating the destination directory
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.
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)
255 def report_not_up_to_date(self):
257 Reports that a build is not up-to-date.
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.
264 print "Build is not up-to-date:"
265 print self.banner('STDOUT ')
267 stderr = self.stderr()
269 print self.banner('STDERR ')
272 def run_gyp(self, gyp_file, *args, **kw):
274 Runs gyp against the specified gyp_file with the specified args.
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)
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)
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)
295 def run(self, *args, **kw):
297 Executes a program by calling the superclass .run() method.
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.
304 if kw.has_key('SYMROOT'):
306 super(TestGypBase, self).run(*args, **kw)
308 def set_configuration(self, configuration):
310 Sets the configuration, to be used for invoking the build
311 tool and testing potential built output.
313 self.configuration = configuration
315 def configuration_dirname(self):
316 if self.configuration:
317 return self.configuration.split('|')[0]
321 def configuration_buildname(self):
322 if self.configuration:
323 return self.configuration
328 # Abstract methods to be defined by format-specific subclasses.
331 def build(self, gyp_file, target=None, **kw):
333 Runs a build of the specified target against the configuration
334 generated from the specified gyp_file.
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.
341 raise NotImplementedError
343 def built_file_path(self, name, type=None, **kw):
345 Returns a path to the specified file name, of the specified type.
347 raise NotImplementedError
349 def built_file_basename(self, name, type=None, **kw):
351 Returns the base name of the specified file name, of the specified type.
353 A bare=True keyword argument specifies that prefixes and suffixes shouldn't
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
365 def run_built_executable(self, name, *args, **kw):
367 Runs an executable program built from a gyp-generated configuration.
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.
373 raise NotImplementedError
375 def up_to_date(self, gyp_file, target=None, **kw):
377 Verifies that a build of the specified target is up to date.
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
384 raise NotImplementedError
387 class TestGypGypd(TestGypBase):
389 Subclass for testing the GYP 'gypd' generator (spit out the
390 internal data structure as pretty-printed Python).
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
400 class TestGypCustom(TestGypBase):
402 Subclass for testing the GYP with custom generator
405 def __init__(self, gyp=None, *args, **kw):
406 self.format = kw.pop("format")
407 super(TestGypCustom, self).__init__(*args, **kw)
410 class TestGypAndroid(TestGypBase):
412 Subclass for testing the GYP Android makefile generator. Note that
413 build/envsetup.sh and lunch must have been run before running tests.
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']
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
426 ALL = 'gyp_all_modules'
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))
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
465 self._device_serial = random.choice(devices)
467 self._device_serial = devices[0]
468 self._call_adb(['root'])
469 self._to_install = set()
471 def target_name(self, target):
472 if target == 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):
480 _INSTALLABLE_PREFIX = 'Install: '
482 def build(self, gyp_file, target=None, **kw):
484 Runs a build using the Android makefiles generated from the specified
485 gyp_file. This logic is taken from Android's mmm.
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']
504 def android_module(self, group, name, subdir):
506 name = '%s_%s' % (subdir, name)
507 if group == 'SHARED_LIBRARIES':
508 name = 'lib_%s' % name
509 return '%s_gyp' % name
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)
515 def built_file_path(self, name, type=None, **kw):
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
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'
539 def _adb_failure(self, command, msg, stdout, stderr):
540 """ Reports a failed adb command and fails the containing test.
543 command: The adb command that failed.
544 msg: The error description.
545 stdout: The standard output.
546 stderr: The standard error.
548 print '%s failed%s' % (' '.join(command), ': %s' % msg if msg else '')
549 print self.banner('STDOUT ')
552 print self.banner('STDERR ')
557 def _call_adb(self, command, timeout=15, retry=3):
558 """ Calls the provided adb command.
560 If the command fails, the test fails.
563 command: The adb command to call.
565 The command's output.
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')
574 command = [command[0], '%s; echo "\n$?";' % ' '.join(command[1:])]
575 adb_command += command
577 for attempt in xrange(1, retry + 1):
582 proc = subprocess.Popen(adb_command, stdout=adb_out, stderr=adb_err)
583 deadline = time.time() + timeout
585 while proc.poll() is None and not timed_out:
587 timed_out = time.time() > deadline
589 print 'Timeout for command %s (attempt %d of %s)' % (
590 adb_command, attempt, retry)
598 if proc.returncode != 0: # returncode is None in the case of a timeout.
600 adb_command, 'retcode=%s' % proc.returncode, adb_out, adb_err)
604 output = adb_out.read()
606 output = output.splitlines(True)
608 output[-2] = output[-2].rstrip('\r\n')
609 output, rc = (''.join(output[:-1]), int(output[-1]))
611 self._adb_failure(adb_command, 'unexpected output format',
614 self._adb_failure(adb_command, 'exited with %d' % rc, adb_out,
618 def run_built_executable(self, name, *args, **kw):
620 Runs an executable program built from a gyp-generated configuration.
622 match = kw.pop('match', self.match)
624 executable_file = self.built_file_path(name, type=self.EXECUTABLE, **kw)
625 if executable_file not in self._to_install:
628 if not self._device_serial:
629 self.skip_test(message='No devices attached.\n')
631 storage = self._call_adb(['shell', 'echo', '$ANDROID_DATA']).strip()
637 for i in self._to_install:
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])
643 if i == executable_file:
644 device_executable = dest
645 self._call_adb(['shell', 'chmod', '755', device_executable])
647 out = self._call_adb(
648 ['shell', 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:%s' % storage,
652 out = out.replace('\r\n', '\n')
653 self._complete(out, kw.pop('stdout', None), None, None, None, match)
656 self._call_adb(['shell', 'rm'] + list(installed))
658 def match_single_line(self, lines = None, expected_line = None):
660 Checks that specified line appears in the text.
662 for line in lines.split('\n'):
663 if line == expected_line:
667 def up_to_date(self, gyp_file, target=None, **kw):
669 Verifies that a build of the specified target is up to date.
671 kw['stdout'] = ("make: Nothing to be done for `%s'." %
672 self.target_name(target))
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)
680 class TestGypCMake(TestGypBase):
682 Subclass for testing the GYP CMake generator, using cmake's ninja backend.
685 build_tool_list = ['cmake']
688 def cmake_build(self, gyp_file, target=None, **kw):
689 arguments = kw.get('arguments', [])[:]
691 self.build_tool_list = ['cmake']
692 self.initialize_build_tool()
694 chdir = os.path.join(kw.get('chdir', '.'),
696 self.configuration_dirname())
699 arguments.append('-G')
700 arguments.append('Ninja')
702 kw['arguments'] = arguments
704 stderr = kw.get('stderr', None)
706 kw['stderr'] = stderr.split('$$$')[0]
708 self.run(program=self.build_tool, **kw)
710 def ninja_build(self, gyp_file, target=None, **kw):
711 arguments = kw.get('arguments', [])[:]
713 self.build_tool_list = ['ninja']
714 self.initialize_build_tool()
716 # Add a -C output/path to the command line.
717 arguments.append('-C')
718 arguments.append(os.path.join('out', self.configuration_dirname()))
720 if target not in (None, self.DEFAULT):
721 arguments.append(target)
723 kw['arguments'] = arguments
725 stderr = kw.get('stderr', None)
727 stderrs = stderr.split('$$$')
728 kw['stderr'] = stderrs[1] if len(stderrs) > 1 else ''
730 return self.run(program=self.build_tool, **kw)
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.
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)
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)
752 def built_file_path(self, name, type=None, **kw):
754 chdir = kw.get('chdir')
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)
771 def up_to_date(self, gyp_file, target=None, **kw):
772 result = self.ninja_build(gyp_file, target, **kw)
774 stdout = self.stdout()
775 if 'ninja: no work to do' not in stdout:
776 self.report_not_up_to_date()
781 class TestGypMake(TestGypBase):
783 Subclass for testing the GYP Make generator.
786 build_tool_list = ['make']
788 def build(self, gyp_file, target=None, **kw):
790 Runs a Make build using the Makefiles generated from the specified
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):
809 Verifies that a build of the specified Make target is up to date.
811 if target in (None, self.DEFAULT):
812 message_target = 'all'
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):
819 Runs an executable built by Make.
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))
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):
836 Returns a path to the specified file name, of the specified type,
839 Built files are in the subdirectory 'out/{configuration}'.
840 The default is 'out/Default'.
842 A chdir= keyword argument specifies the source directory
843 relative to which the output subdirectory can be found.
845 "type" values of STATIC_LIB or SHARED_LIB append the necessary
846 prefixes and suffixes to a platform-independent library base name.
848 A subdir= keyword argument specifies a library subdirectory within
849 the default 'obj.target'.
852 chdir = kw.get('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)
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()
876 def FindMSBuildInstallation(msvs_version = 'auto'):
877 """Returns path to MSBuild for msvs_version or latest available.
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.
883 registry = TestWin.Registry()
887 '2012': r'4.0', # Really v4.0.30319 which comes with .NET 4.5.
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'
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
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
910 if not msbuild_version:
911 print 'Error: could not find MSBuild registry entry'
914 msbuild_path = registry.GetValue(msbuild_basekey + '\\' + msbuild_version,
917 print 'Error: could not get MSBuild registry entry value'
920 return os.path.join(msbuild_path, 'MSBuild.exe')
923 def FindVisualStudioInstallation():
924 """Returns appropriate values for .build_tool and .uses_msbuild fields
925 of TestGypBase for Visual Studio.
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.
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)']]
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'}
941 possible_roots = [ConvertToCygpath(r) for r in possible_roots]
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)
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
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'
973 class TestGypOnMSToolchain(TestGypBase):
975 Common subclass for testing generators that target the Microsoft Visual
976 Studio toolchain (cl, link, dumpbin, etc.)
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')
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(
992 def run_dumpbin(self, *dumpbin_args):
993 """Run the dumpbin tool with the specified arguments, and capturing and
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
1004 class TestGypNinja(TestGypOnMSToolchain):
1006 Subclass for testing the GYP Ninja generator.
1009 build_tool_list = ['ninja']
1013 def run_gyp(self, gyp_file, *args, **kw):
1014 TestGypBase.run_gyp(self, gyp_file, *args, **kw)
1016 def build(self, gyp_file, target=None, **kw):
1017 arguments = kw.get('arguments', [])[:]
1019 # Add a -C output/path to the command line.
1020 arguments.append('-C')
1021 arguments.append(os.path.join('out', self.configuration_dirname()))
1025 arguments.append(target)
1027 kw['arguments'] = arguments
1028 return self.run(program=self.build_tool, **kw)
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)
1038 def built_file_path(self, name, type=None, **kw):
1040 chdir = kw.get('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)
1057 def up_to_date(self, gyp_file, target=None, **kw):
1058 result = self.build(gyp_file, target, **kw)
1060 stdout = self.stdout()
1061 if 'ninja: no work to do' not in stdout:
1062 self.report_not_up_to_date()
1067 class TestGypMSVS(TestGypOnMSToolchain):
1069 Subclass for testing the GYP Visual Studio generator.
1073 u = r'=== Build: 0 succeeded, 0 failed, (\d+) up-to-date, 0 skipped ==='
1074 up_to_date_re = re.compile(u, re.M)
1076 # Initial None element will indicate to our .initialize_build_tool()
1077 # method below that 'devenv' was not found on %PATH%.
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']
1083 def initialize_build_tool(self):
1084 super(TestGypMSVS, self).initialize_build_tool()
1085 self.build_tool = self.devenv_path
1087 def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
1089 Runs a Visual Studio build using the configuration generated
1090 from the specified gyp_file.
1092 configuration = self.configuration_buildname()
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):
1112 Verifies that a build of the specified Visual Studio target is up to date.
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.
1122 The workaround is to specify a workdir when instantiating the test, e.g.
1123 test = TestGyp.TestGyp(workdir='workarea')
1125 result = self.build(gyp_file, target, **kw)
1127 stdout = self.stdout()
1129 m = self.up_to_date_re.search(stdout)
1130 up_to_date = m and int(m.group(1)) > 0
1132 self.report_not_up_to_date()
1135 def run_built_executable(self, name, *args, **kw):
1137 Runs an executable built by Visual Studio.
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):
1145 Returns a path to the specified file name, of the specified type,
1146 as built by Visual Studio.
1148 Built files are in a subdirectory that matches the configuration
1149 name. The default is 'Default'.
1151 A chdir= keyword argument specifies the source directory
1152 relative to which the output subdirectory can be found.
1154 "type" values of STATIC_LIB or SHARED_LIB append the necessary
1155 prefixes and suffixes to a platform-independent library base name.
1158 chdir = kw.get('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)
1168 class TestGypMSVSNinja(TestGypNinja):
1170 Subclass for testing the GYP Visual Studio Ninja generator.
1172 format = 'msvs-ninja'
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')
1179 def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
1181 Runs a Visual Studio build using the configuration generated
1182 from the specified gyp_file.
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')])
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'])
1205 arguments.extend(['/target:'+build])
1206 configuration = self.configuration_buildname()
1207 config = configuration.split('|')
1208 arguments.extend(['/property:Configuration='+config[0]])
1210 arguments.extend(['/property:Platform='+config[1]])
1211 arguments.extend(['/property:BuildInParallel=false'])
1212 arguments.extend(['/verbosity:minimal'])
1214 kw['arguments'] = arguments
1215 return self.run(program=self.msbuild_path, **kw)
1218 class TestGypXcode(TestGypBase):
1220 Subclass for testing the GYP Xcode generator.
1223 build_tool_list = ['xcodebuild']
1225 phase_script_execution = ("\n"
1226 "PhaseScriptExecution /\\S+/Script-[0-9A-F]+\\.sh\n"
1228 " /bin/sh -c /\\S+/Script-[0-9A-F]+\\.sh\n"
1229 "(make: Nothing to be done for `all'\\.\n)?")
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),
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),
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
1248 def build(self, gyp_file, target=None, **kw):
1250 Runs an xcodebuild using the .xcodeproj generated from the specified
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')
1265 arguments.append('SYMROOT='+symroot)
1266 kw['arguments'] = arguments
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):
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
1281 return self.run(program=self.build_tool, **kw)
1282 def up_to_date(self, gyp_file, target=None, **kw):
1284 Verifies that a build of the specified Xcode target is up to date.
1286 result = self.build(gyp_file, target, **kw)
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()
1295 def run_built_executable(self, name, *args, **kw):
1297 Runs an executable built by xcodebuild.
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):
1306 Returns a path to the specified file name, of the specified type,
1309 Built files are in the subdirectory 'build/{configuration}'.
1310 The default is 'build/Default'.
1312 A chdir= keyword argument specifies the source directory
1313 relative to which the output subdirectory can be found.
1315 "type" values of STATIC_LIB or SHARED_LIB append the necessary
1316 prefixes and suffixes to a platform-independent library base name.
1319 chdir = kw.get('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)
1328 format_class_list = [
1339 def TestGyp(*args, **kw):
1341 Returns an appropriate TestGyp* instance for a specified GYP format.
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