1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420 |
- # Copyright (c) 2012 Google Inc. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """
- TestGyp.py: a testing framework for GYP integration tests.
- """
- import collections
- from contextlib import contextmanager
- import itertools
- import os
- import random
- import re
- import shutil
- import stat
- import subprocess
- import sys
- import tempfile
- import time
- import TestCmd
- import TestCommon
- from TestCommon import __all__
- __all__.extend([
- 'TestGyp',
- ])
- def remove_debug_line_numbers(contents):
- """Function to remove the line numbers from the debug output
- of gyp and thus reduce the extreme fragility of the stdout
- comparison tests.
- """
- lines = contents.splitlines()
- # split each line on ":"
- lines = [l.split(":", 3) for l in lines]
- # join each line back together while ignoring the
- # 3rd column which is the line number
- lines = [len(l) > 3 and ":".join(l[3:]) or l for l in lines]
- return "\n".join(lines)
- def match_modulo_line_numbers(contents_a, contents_b):
- """File contents matcher that ignores line numbers."""
- contents_a = remove_debug_line_numbers(contents_a)
- contents_b = remove_debug_line_numbers(contents_b)
- return TestCommon.match_exact(contents_a, contents_b)
- @contextmanager
- def LocalEnv(local_env):
- """Context manager to provide a local OS environment."""
- old_env = os.environ.copy()
- os.environ.update(local_env)
- try:
- yield
- finally:
- os.environ.clear()
- os.environ.update(old_env)
- class TestGypBase(TestCommon.TestCommon):
- """
- Class for controlling end-to-end tests of gyp generators.
- Instantiating this class will create a temporary directory and
- arrange for its destruction (via the TestCmd superclass) and
- copy all of the non-gyptest files in the directory hierarchy of the
- executing script.
- The default behavior is to test the 'gyp' or 'gyp.bat' file in the
- current directory. An alternative may be specified explicitly on
- instantiation, or by setting the TESTGYP_GYP environment variable.
- This class should be subclassed for each supported gyp generator
- (format). Various abstract methods below define calling signatures
- used by the test scripts to invoke builds on the generated build
- configuration and to run executables generated by those builds.
- """
- formats = []
- build_tool = None
- build_tool_list = []
- _exe = TestCommon.exe_suffix
- _obj = TestCommon.obj_suffix
- shobj_ = TestCommon.shobj_prefix
- _shobj = TestCommon.shobj_suffix
- lib_ = TestCommon.lib_prefix
- _lib = TestCommon.lib_suffix
- dll_ = TestCommon.dll_prefix
- _dll = TestCommon.dll_suffix
- # Constants to represent different targets.
- ALL = '__all__'
- DEFAULT = '__default__'
- # Constants for different target types.
- EXECUTABLE = '__executable__'
- STATIC_LIB = '__static_lib__'
- SHARED_LIB = '__shared_lib__'
- def __init__(self, gyp=None, *args, **kw):
- self.origin_cwd = os.path.abspath(os.path.dirname(sys.argv[0]))
- self.extra_args = sys.argv[1:]
- if not gyp:
- gyp = os.environ.get('TESTGYP_GYP')
- if not gyp:
- if sys.platform == 'win32':
- gyp = 'gyp.bat'
- else:
- gyp = 'gyp'
- self.gyp = os.path.abspath(gyp)
- self.no_parallel = False
- self.formats = [self.format]
- self.initialize_build_tool()
- kw.setdefault('match', TestCommon.match_exact)
- # Put test output in out/testworkarea by default.
- # Use temporary names so there are no collisions.
- workdir = os.path.join('out', kw.get('workdir', 'testworkarea'))
- # Create work area if it doesn't already exist.
- if not os.path.isdir(workdir):
- os.makedirs(workdir)
- kw['workdir'] = tempfile.mktemp(prefix='testgyp.', dir=workdir)
- formats = kw.pop('formats', [])
- super(TestGypBase, self).__init__(*args, **kw)
- real_format = self.format.split('-')[-1]
- excluded_formats = set([f for f in formats if f[0] == '!'])
- included_formats = set(formats) - excluded_formats
- if ('!'+real_format in excluded_formats or
- included_formats and real_format not in included_formats):
- msg = 'Invalid test for %r format; skipping test.\n'
- self.skip_test(msg % self.format)
- self.copy_test_configuration(self.origin_cwd, self.workdir)
- self.set_configuration(None)
- # Set $HOME so that gyp doesn't read the user's actual
- # ~/.gyp/include.gypi file, which may contain variables
- # and other settings that would change the output.
- os.environ['HOME'] = self.workpath()
- # Clear $GYP_DEFINES for the same reason.
- if 'GYP_DEFINES' in os.environ:
- del os.environ['GYP_DEFINES']
- # Override the user's language settings, which could
- # otherwise make the output vary from what is expected.
- os.environ['LC_ALL'] = 'C'
- def built_file_must_exist(self, name, type=None, **kw):
- """
- Fails the test if the specified built file name does not exist.
- """
- return self.must_exist(self.built_file_path(name, type, **kw))
- def built_file_must_not_exist(self, name, type=None, **kw):
- """
- Fails the test if the specified built file name exists.
- """
- return self.must_not_exist(self.built_file_path(name, type, **kw))
- def built_file_must_match(self, name, contents, **kw):
- """
- Fails the test if the contents of the specified built file name
- do not match the specified contents.
- """
- return self.must_match(self.built_file_path(name, **kw), contents)
- def built_file_must_not_match(self, name, contents, **kw):
- """
- Fails the test if the contents of the specified built file name
- match the specified contents.
- """
- return self.must_not_match(self.built_file_path(name, **kw), contents)
- def built_file_must_not_contain(self, name, contents, **kw):
- """
- Fails the test if the specified built file name contains the specified
- contents.
- """
- return self.must_not_contain(self.built_file_path(name, **kw), contents)
- def copy_test_configuration(self, source_dir, dest_dir):
- """
- Copies the test configuration from the specified source_dir
- (the directory in which the test script lives) to the
- specified dest_dir (a temporary working directory).
- This ignores all files and directories that begin with
- the string 'gyptest', and all '.svn' subdirectories.
- """
- for root, dirs, files in os.walk(source_dir):
- if '.svn' in dirs:
- dirs.remove('.svn')
- dirs = [ d for d in dirs if not d.startswith('gyptest') ]
- files = [ f for f in files if not f.startswith('gyptest') ]
- for dirname in dirs:
- source = os.path.join(root, dirname)
- destination = source.replace(source_dir, dest_dir)
- os.mkdir(destination)
- if sys.platform != 'win32':
- shutil.copystat(source, destination)
- for filename in files:
- source = os.path.join(root, filename)
- destination = source.replace(source_dir, dest_dir)
- shutil.copy2(source, destination)
- def initialize_build_tool(self):
- """
- Initializes the .build_tool attribute.
- Searches the .build_tool_list for an executable name on the user's
- $PATH. The first tool on the list is used as-is if nothing is found
- on the current $PATH.
- """
- for build_tool in self.build_tool_list:
- if not build_tool:
- continue
- if os.path.isabs(build_tool):
- self.build_tool = build_tool
- return
- build_tool = self.where_is(build_tool)
- if build_tool:
- self.build_tool = build_tool
- return
- if self.build_tool_list:
- self.build_tool = self.build_tool_list[0]
- def relocate(self, source, destination):
- """
- Renames (relocates) the specified source (usually a directory)
- to the specified destination, creating the destination directory
- first if necessary.
- Note: Don't use this as a generic "rename" operation. In the
- future, "relocating" parts of a GYP tree may affect the state of
- the test to modify the behavior of later method calls.
- """
- destination_dir = os.path.dirname(destination)
- if not os.path.exists(destination_dir):
- self.subdir(destination_dir)
- os.rename(source, destination)
- def report_not_up_to_date(self):
- """
- Reports that a build is not up-to-date.
- This provides common reporting for formats that have complicated
- conditions for checking whether a build is up-to-date. Formats
- that expect exact output from the command (make) can
- just set stdout= when they call the run_build() method.
- """
- print "Build is not up-to-date:"
- print self.banner('STDOUT ')
- print self.stdout()
- stderr = self.stderr()
- if stderr:
- print self.banner('STDERR ')
- print stderr
- def run_gyp(self, gyp_file, *args, **kw):
- """
- Runs gyp against the specified gyp_file with the specified args.
- """
- # When running gyp, and comparing its output we use a comparitor
- # that ignores the line numbers that gyp logs in its debug output.
- if kw.pop('ignore_line_numbers', False):
- kw.setdefault('match', match_modulo_line_numbers)
- # TODO: --depth=. works around Chromium-specific tree climbing.
- depth = kw.pop('depth', '.')
- run_args = ['--depth='+depth]
- run_args.extend(['--format='+f for f in self.formats]);
- run_args.append(gyp_file)
- if self.no_parallel:
- run_args += ['--no-parallel']
- # TODO: if extra_args contains a '--build' flag
- # we really want that to only apply to the last format (self.format).
- run_args.extend(self.extra_args)
- # Default xcode_ninja_target_pattern to ^.*$ to fix xcode-ninja tests
- xcode_ninja_target_pattern = kw.pop('xcode_ninja_target_pattern', '.*')
- run_args.extend(
- ['-G', 'xcode_ninja_target_pattern=%s' % xcode_ninja_target_pattern])
- run_args.extend(args)
- return self.run(program=self.gyp, arguments=run_args, **kw)
- def run(self, *args, **kw):
- """
- Executes a program by calling the superclass .run() method.
- This exists to provide a common place to filter out keyword
- arguments implemented in this layer, without having to update
- the tool-specific subclasses or clutter the tests themselves
- with platform-specific code.
- """
- if kw.has_key('SYMROOT'):
- del kw['SYMROOT']
- super(TestGypBase, self).run(*args, **kw)
- def set_configuration(self, configuration):
- """
- Sets the configuration, to be used for invoking the build
- tool and testing potential built output.
- """
- self.configuration = configuration
- def configuration_dirname(self):
- if self.configuration:
- return self.configuration.split('|')[0]
- else:
- return 'Default'
- def configuration_buildname(self):
- if self.configuration:
- return self.configuration
- else:
- return 'Default'
- #
- # Abstract methods to be defined by format-specific subclasses.
- #
- def build(self, gyp_file, target=None, **kw):
- """
- Runs a build of the specified target against the configuration
- generated from the specified gyp_file.
- A 'target' argument of None or the special value TestGyp.DEFAULT
- specifies the default argument for the underlying build tool.
- A 'target' argument of TestGyp.ALL specifies the 'all' target
- (if any) of the underlying build tool.
- """
- raise NotImplementedError
- def built_file_path(self, name, type=None, **kw):
- """
- Returns a path to the specified file name, of the specified type.
- """
- raise NotImplementedError
- def built_file_basename(self, name, type=None, **kw):
- """
- Returns the base name of the specified file name, of the specified type.
- A bare=True keyword argument specifies that prefixes and suffixes shouldn't
- be applied.
- """
- if not kw.get('bare'):
- if type == self.EXECUTABLE:
- name = name + self._exe
- elif type == self.STATIC_LIB:
- name = self.lib_ + name + self._lib
- elif type == self.SHARED_LIB:
- name = self.dll_ + name + self._dll
- return name
- def run_built_executable(self, name, *args, **kw):
- """
- Runs an executable program built from a gyp-generated configuration.
- The specified name should be independent of any particular generator.
- Subclasses should find the output executable in the appropriate
- output build directory, tack on any necessary executable suffix, etc.
- """
- raise NotImplementedError
- def up_to_date(self, gyp_file, target=None, **kw):
- """
- Verifies that a build of the specified target is up to date.
- The subclass should implement this by calling build()
- (or a reasonable equivalent), checking whatever conditions
- will tell it the build was an "up to date" null build, and
- failing if it isn't.
- """
- raise NotImplementedError
- class TestGypGypd(TestGypBase):
- """
- Subclass for testing the GYP 'gypd' generator (spit out the
- internal data structure as pretty-printed Python).
- """
- format = 'gypd'
- def __init__(self, gyp=None, *args, **kw):
- super(TestGypGypd, self).__init__(*args, **kw)
- # gypd implies the use of 'golden' files, so parallelizing conflicts as it
- # causes ordering changes.
- self.no_parallel = True
- class TestGypCustom(TestGypBase):
- """
- Subclass for testing the GYP with custom generator
- """
- def __init__(self, gyp=None, *args, **kw):
- self.format = kw.pop("format")
- super(TestGypCustom, self).__init__(*args, **kw)
- class TestGypAndroid(TestGypBase):
- """
- Subclass for testing the GYP Android makefile generator. Note that
- build/envsetup.sh and lunch must have been run before running tests.
- """
- format = 'android'
- # Note that we can't use mmm as the build tool because ...
- # - it builds all targets, whereas we need to pass a target
- # - it is a function, whereas the test runner assumes the build tool is a file
- # Instead we use make and duplicate the logic from mmm.
- build_tool_list = ['make']
- # We use our custom target 'gyp_all_modules', as opposed to the 'all_modules'
- # target used by mmm, to build only those targets which are part of the gyp
- # target 'all'.
- ALL = 'gyp_all_modules'
- def __init__(self, gyp=None, *args, **kw):
- # Android requires build and test output to be inside its source tree.
- # We use the following working directory for the test's source, but the
- # test's build output still goes to $ANDROID_PRODUCT_OUT.
- # Note that some tests explicitly set format='gypd' to invoke the gypd
- # backend. This writes to the source tree, but there's no way around this.
- kw['workdir'] = os.path.join('/tmp', 'gyptest',
- kw.get('workdir', 'testworkarea'))
- # We need to remove all gyp outputs from out/. Ths is because some tests
- # don't have rules to regenerate output, so they will simply re-use stale
- # output if present. Since the test working directory gets regenerated for
- # each test run, this can confuse things.
- # We don't have a list of build outputs because we don't know which
- # dependent targets were built. Instead we delete all gyp-generated output.
- # This may be excessive, but should be safe.
- out_dir = os.environ['ANDROID_PRODUCT_OUT']
- obj_dir = os.path.join(out_dir, 'obj')
- shutil.rmtree(os.path.join(obj_dir, 'GYP'), ignore_errors = True)
- for x in ['EXECUTABLES', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES']:
- for d in os.listdir(os.path.join(obj_dir, x)):
- if d.endswith('_gyp_intermediates'):
- shutil.rmtree(os.path.join(obj_dir, x, d), ignore_errors = True)
- for x in [os.path.join('obj', 'lib'), os.path.join('system', 'lib')]:
- for d in os.listdir(os.path.join(out_dir, x)):
- if d.endswith('_gyp.so'):
- os.remove(os.path.join(out_dir, x, d))
- super(TestGypAndroid, self).__init__(*args, **kw)
- self._adb_path = os.path.join(os.environ['ANDROID_HOST_OUT'], 'bin', 'adb')
- self._device_serial = None
- adb_devices_out = self._call_adb(['devices'])
- devices = [l.split()[0] for l in adb_devices_out.splitlines()[1:-1]
- if l.split()[1] == 'device']
- if len(devices) == 0:
- self._device_serial = None
- else:
- if len(devices) > 1:
- self._device_serial = random.choice(devices)
- else:
- self._device_serial = devices[0]
- self._call_adb(['root'])
- self._to_install = set()
- def target_name(self, target):
- if target == self.ALL:
- return self.ALL
- # The default target is 'droid'. However, we want to use our special target
- # to build only the gyp target 'all'.
- if target in (None, self.DEFAULT):
- return self.ALL
- return target
- _INSTALLABLE_PREFIX = 'Install: '
- def build(self, gyp_file, target=None, **kw):
- """
- Runs a build using the Android makefiles generated from the specified
- gyp_file. This logic is taken from Android's mmm.
- """
- arguments = kw.get('arguments', [])[:]
- arguments.append(self.target_name(target))
- arguments.append('-C')
- arguments.append(os.environ['ANDROID_BUILD_TOP'])
- kw['arguments'] = arguments
- chdir = kw.get('chdir', '')
- makefile = os.path.join(self.workdir, chdir, 'GypAndroid.mk')
- os.environ['ONE_SHOT_MAKEFILE'] = makefile
- result = self.run(program=self.build_tool, **kw)
- for l in self.stdout().splitlines():
- if l.startswith(TestGypAndroid._INSTALLABLE_PREFIX):
- self._to_install.add(os.path.abspath(os.path.join(
- os.environ['ANDROID_BUILD_TOP'],
- l[len(TestGypAndroid._INSTALLABLE_PREFIX):])))
- del os.environ['ONE_SHOT_MAKEFILE']
- return result
- def android_module(self, group, name, subdir):
- if subdir:
- name = '%s_%s' % (subdir, name)
- if group == 'SHARED_LIBRARIES':
- name = 'lib_%s' % name
- return '%s_gyp' % name
- def intermediates_dir(self, group, module_name):
- return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', group,
- '%s_intermediates' % module_name)
- def built_file_path(self, name, type=None, **kw):
- """
- Returns a path to the specified file name, of the specified type,
- as built by Android. Note that we don't support the configuration
- parameter.
- """
- # Built files are in $ANDROID_PRODUCT_OUT. This requires copying logic from
- # the Android build system.
- if type == None or type == self.EXECUTABLE:
- return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', 'GYP',
- 'shared_intermediates', name)
- subdir = kw.get('subdir')
- if type == self.STATIC_LIB:
- group = 'STATIC_LIBRARIES'
- module_name = self.android_module(group, name, subdir)
- return os.path.join(self.intermediates_dir(group, module_name),
- '%s.a' % module_name)
- if type == self.SHARED_LIB:
- group = 'SHARED_LIBRARIES'
- module_name = self.android_module(group, name, subdir)
- return os.path.join(self.intermediates_dir(group, module_name), 'LINKED',
- '%s.so' % module_name)
- assert False, 'Unhandled type'
- def _adb_failure(self, command, msg, stdout, stderr):
- """ Reports a failed adb command and fails the containing test.
- Args:
- command: The adb command that failed.
- msg: The error description.
- stdout: The standard output.
- stderr: The standard error.
- """
- print '%s failed%s' % (' '.join(command), ': %s' % msg if msg else '')
- print self.banner('STDOUT ')
- stdout.seek(0)
- print stdout.read()
- print self.banner('STDERR ')
- stderr.seek(0)
- print stderr.read()
- self.fail_test()
- def _call_adb(self, command, timeout=15, retry=3):
- """ Calls the provided adb command.
- If the command fails, the test fails.
- Args:
- command: The adb command to call.
- Returns:
- The command's output.
- """
- with tempfile.TemporaryFile(bufsize=0) as adb_out:
- with tempfile.TemporaryFile(bufsize=0) as adb_err:
- adb_command = [self._adb_path]
- if self._device_serial:
- adb_command += ['-s', self._device_serial]
- is_shell = (command[0] == 'shell')
- if is_shell:
- command = [command[0], '%s; echo "\n$?";' % ' '.join(command[1:])]
- adb_command += command
- for attempt in xrange(1, retry + 1):
- adb_out.seek(0)
- adb_out.truncate(0)
- adb_err.seek(0)
- adb_err.truncate(0)
- proc = subprocess.Popen(adb_command, stdout=adb_out, stderr=adb_err)
- deadline = time.time() + timeout
- timed_out = False
- while proc.poll() is None and not timed_out:
- time.sleep(1)
- timed_out = time.time() > deadline
- if timed_out:
- print 'Timeout for command %s (attempt %d of %s)' % (
- adb_command, attempt, retry)
- try:
- proc.kill()
- except:
- pass
- else:
- break
- if proc.returncode != 0: # returncode is None in the case of a timeout.
- self._adb_failure(
- adb_command, 'retcode=%s' % proc.returncode, adb_out, adb_err)
- return
- adb_out.seek(0)
- output = adb_out.read()
- if is_shell:
- output = output.splitlines(True)
- try:
- output[-2] = output[-2].rstrip('\r\n')
- output, rc = (''.join(output[:-1]), int(output[-1]))
- except ValueError:
- self._adb_failure(adb_command, 'unexpected output format',
- adb_out, adb_err)
- if rc != 0:
- self._adb_failure(adb_command, 'exited with %d' % rc, adb_out,
- adb_err)
- return output
- def run_built_executable(self, name, *args, **kw):
- """
- Runs an executable program built from a gyp-generated configuration.
- """
- match = kw.pop('match', self.match)
- executable_file = self.built_file_path(name, type=self.EXECUTABLE, **kw)
- if executable_file not in self._to_install:
- self.fail_test()
- if not self._device_serial:
- self.skip_test(message='No devices attached.\n')
- storage = self._call_adb(['shell', 'echo', '$ANDROID_DATA']).strip()
- if not len(storage):
- self.fail_test()
- installed = set()
- try:
- for i in self._to_install:
- a = os.path.abspath(
- os.path.join(os.environ['ANDROID_BUILD_TOP'], i))
- dest = '%s/%s' % (storage, os.path.basename(a))
- self._call_adb(['push', os.path.abspath(a), dest])
- installed.add(dest)
- if i == executable_file:
- device_executable = dest
- self._call_adb(['shell', 'chmod', '755', device_executable])
- out = self._call_adb(
- ['shell', 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:%s' % storage,
- device_executable],
- timeout=60,
- retry=1)
- out = out.replace('\r\n', '\n')
- self._complete(out, kw.pop('stdout', None), None, None, None, match)
- finally:
- if len(installed):
- self._call_adb(['shell', 'rm'] + list(installed))
- def match_single_line(self, lines = None, expected_line = None):
- """
- Checks that specified line appears in the text.
- """
- for line in lines.split('\n'):
- if line == expected_line:
- return 1
- return
- def up_to_date(self, gyp_file, target=None, **kw):
- """
- Verifies that a build of the specified target is up to date.
- """
- kw['stdout'] = ("make: Nothing to be done for `%s'." %
- self.target_name(target))
- # We need to supply a custom matcher, since we don't want to depend on the
- # exact stdout string.
- kw['match'] = self.match_single_line
- return self.build(gyp_file, target, **kw)
- class TestGypCMake(TestGypBase):
- """
- Subclass for testing the GYP CMake generator, using cmake's ninja backend.
- """
- format = 'cmake'
- build_tool_list = ['cmake']
- ALL = 'all'
- def cmake_build(self, gyp_file, target=None, **kw):
- arguments = kw.get('arguments', [])[:]
- self.build_tool_list = ['cmake']
- self.initialize_build_tool()
- chdir = os.path.join(kw.get('chdir', '.'),
- 'out',
- self.configuration_dirname())
- kw['chdir'] = chdir
- arguments.append('-G')
- arguments.append('Ninja')
- kw['arguments'] = arguments
- stderr = kw.get('stderr', None)
- if stderr:
- kw['stderr'] = stderr.split('$$$')[0]
- self.run(program=self.build_tool, **kw)
- def ninja_build(self, gyp_file, target=None, **kw):
- arguments = kw.get('arguments', [])[:]
- self.build_tool_list = ['ninja']
- self.initialize_build_tool()
- # Add a -C output/path to the command line.
- arguments.append('-C')
- arguments.append(os.path.join('out', self.configuration_dirname()))
- if target not in (None, self.DEFAULT):
- arguments.append(target)
- kw['arguments'] = arguments
- stderr = kw.get('stderr', None)
- if stderr:
- stderrs = stderr.split('$$$')
- kw['stderr'] = stderrs[1] if len(stderrs) > 1 else ''
- return self.run(program=self.build_tool, **kw)
- def build(self, gyp_file, target=None, status=0, **kw):
- # Two tools must be run to build, cmake and the ninja.
- # Allow cmake to succeed when the overall expectation is to fail.
- if status is None:
- kw['status'] = None
- else:
- if not isinstance(status, collections.Iterable): status = (status,)
- kw['status'] = list(itertools.chain((0,), status))
- self.cmake_build(gyp_file, target, **kw)
- kw['status'] = status
- self.ninja_build(gyp_file, target, **kw)
- def run_built_executable(self, name, *args, **kw):
- # Enclosing the name in a list avoids prepending the original dir.
- program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
- if sys.platform == 'darwin':
- configuration = self.configuration_dirname()
- os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
- return self.run(program=program, *args, **kw)
- def built_file_path(self, name, type=None, **kw):
- result = []
- chdir = kw.get('chdir')
- if chdir:
- result.append(chdir)
- result.append('out')
- result.append(self.configuration_dirname())
- if type == self.STATIC_LIB:
- if sys.platform != 'darwin':
- result.append('obj.target')
- elif type == self.SHARED_LIB:
- if sys.platform != 'darwin' and sys.platform != 'win32':
- result.append('lib.target')
- subdir = kw.get('subdir')
- if subdir and type != self.SHARED_LIB:
- result.append(subdir)
- result.append(self.built_file_basename(name, type, **kw))
- return self.workpath(*result)
- def up_to_date(self, gyp_file, target=None, **kw):
- result = self.ninja_build(gyp_file, target, **kw)
- if not result:
- stdout = self.stdout()
- if 'ninja: no work to do' not in stdout:
- self.report_not_up_to_date()
- self.fail_test()
- return result
- class TestGypMake(TestGypBase):
- """
- Subclass for testing the GYP Make generator.
- """
- format = 'make'
- build_tool_list = ['make']
- ALL = 'all'
- def build(self, gyp_file, target=None, **kw):
- """
- Runs a Make build using the Makefiles generated from the specified
- gyp_file.
- """
- arguments = kw.get('arguments', [])[:]
- if self.configuration:
- arguments.append('BUILDTYPE=' + self.configuration)
- if target not in (None, self.DEFAULT):
- arguments.append(target)
- # Sub-directory builds provide per-gyp Makefiles (i.e.
- # Makefile.gyp_filename), so use that if there is no Makefile.
- chdir = kw.get('chdir', '')
- if not os.path.exists(os.path.join(chdir, 'Makefile')):
- print "NO Makefile in " + os.path.join(chdir, 'Makefile')
- arguments.insert(0, '-f')
- arguments.insert(1, os.path.splitext(gyp_file)[0] + '.Makefile')
- kw['arguments'] = arguments
- return self.run(program=self.build_tool, **kw)
- def up_to_date(self, gyp_file, target=None, **kw):
- """
- Verifies that a build of the specified Make target is up to date.
- """
- if target in (None, self.DEFAULT):
- message_target = 'all'
- else:
- message_target = target
- kw['stdout'] = "make: Nothing to be done for `%s'.\n" % message_target
- return self.build(gyp_file, target, **kw)
- def run_built_executable(self, name, *args, **kw):
- """
- Runs an executable built by Make.
- """
- configuration = self.configuration_dirname()
- libdir = os.path.join('out', configuration, 'lib')
- # TODO(piman): when everything is cross-compile safe, remove lib.target
- if sys.platform == 'darwin':
- # Mac puts target shared libraries right in the product directory.
- configuration = self.configuration_dirname()
- os.environ['DYLD_LIBRARY_PATH'] = (
- libdir + '.host:' + os.path.join('out', configuration))
- else:
- os.environ['LD_LIBRARY_PATH'] = libdir + '.host:' + libdir + '.target'
- # Enclosing the name in a list avoids prepending the original dir.
- program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
- return self.run(program=program, *args, **kw)
- def built_file_path(self, name, type=None, **kw):
- """
- Returns a path to the specified file name, of the specified type,
- as built by Make.
- Built files are in the subdirectory 'out/{configuration}'.
- The default is 'out/Default'.
- A chdir= keyword argument specifies the source directory
- relative to which the output subdirectory can be found.
- "type" values of STATIC_LIB or SHARED_LIB append the necessary
- prefixes and suffixes to a platform-independent library base name.
- A subdir= keyword argument specifies a library subdirectory within
- the default 'obj.target'.
- """
- result = []
- chdir = kw.get('chdir')
- if chdir:
- result.append(chdir)
- configuration = self.configuration_dirname()
- result.extend(['out', configuration])
- if type == self.STATIC_LIB and sys.platform != 'darwin':
- result.append('obj.target')
- elif type == self.SHARED_LIB and sys.platform != 'darwin':
- result.append('lib.target')
- subdir = kw.get('subdir')
- if subdir and type != self.SHARED_LIB:
- result.append(subdir)
- result.append(self.built_file_basename(name, type, **kw))
- return self.workpath(*result)
- def ConvertToCygpath(path):
- """Convert to cygwin path if we are using cygwin."""
- if sys.platform == 'cygwin':
- p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE)
- path = p.communicate()[0].strip()
- return path
- def FindMSBuildInstallation(msvs_version = 'auto'):
- """Returns path to MSBuild for msvs_version or latest available.
- Looks in the registry to find install location of MSBuild.
- MSBuild before v4.0 will not build c++ projects, so only use newer versions.
- """
- import TestWin
- registry = TestWin.Registry()
- msvs_to_msbuild = {
- '2013': r'12.0',
- '2012': r'4.0', # Really v4.0.30319 which comes with .NET 4.5.
- '2010': r'4.0'}
- msbuild_basekey = r'HKLM\SOFTWARE\Microsoft\MSBuild\ToolsVersions'
- if not registry.KeyExists(msbuild_basekey):
- print 'Error: could not find MSBuild base registry entry'
- return None
- msbuild_version = None
- if msvs_version in msvs_to_msbuild:
- msbuild_test_version = msvs_to_msbuild[msvs_version]
- if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
- msbuild_version = msbuild_test_version
- else:
- print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
- 'but corresponding MSBuild "%s" was not found.' %
- (msvs_version, msbuild_version))
- if not msbuild_version:
- for msvs_version in sorted(msvs_to_msbuild, reverse=True):
- msbuild_test_version = msvs_to_msbuild[msvs_version]
- if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
- msbuild_version = msbuild_test_version
- break
- if not msbuild_version:
- print 'Error: could not find MSBuild registry entry'
- return None
- msbuild_path = registry.GetValue(msbuild_basekey + '\\' + msbuild_version,
- 'MSBuildToolsPath')
- if not msbuild_path:
- print 'Error: could not get MSBuild registry entry value'
- return None
- return os.path.join(msbuild_path, 'MSBuild.exe')
- def FindVisualStudioInstallation():
- """Returns appropriate values for .build_tool and .uses_msbuild fields
- of TestGypBase for Visual Studio.
- We use the value specified by GYP_MSVS_VERSION. If not specified, we
- search %PATH% and %PATHEXT% for a devenv.{exe,bat,...} executable.
- Failing that, we search for likely deployment paths.
- """
- possible_roots = ['%s:\\Program Files%s' % (chr(drive), suffix)
- for drive in range(ord('C'), ord('Z') + 1)
- for suffix in ['', ' (x86)']]
- possible_paths = {
- '2013': r'Microsoft Visual Studio 12.0\Common7\IDE\devenv.com',
- '2012': r'Microsoft Visual Studio 11.0\Common7\IDE\devenv.com',
- '2010': r'Microsoft Visual Studio 10.0\Common7\IDE\devenv.com',
- '2008': r'Microsoft Visual Studio 9.0\Common7\IDE\devenv.com',
- '2005': r'Microsoft Visual Studio 8\Common7\IDE\devenv.com'}
- possible_roots = [ConvertToCygpath(r) for r in possible_roots]
- msvs_version = 'auto'
- for flag in (f for f in sys.argv if f.startswith('msvs_version=')):
- msvs_version = flag.split('=')[-1]
- msvs_version = os.environ.get('GYP_MSVS_VERSION', msvs_version)
- if msvs_version in possible_paths:
- # Check that the path to the specified GYP_MSVS_VERSION exists.
- path = possible_paths[msvs_version]
- for r in possible_roots:
- build_tool = os.path.join(r, path)
- if os.path.exists(build_tool):
- uses_msbuild = msvs_version >= '2010'
- msbuild_path = FindMSBuildInstallation(msvs_version)
- return build_tool, uses_msbuild, msbuild_path
- else:
- print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
- 'but corresponding "%s" was not found.' % (msvs_version, path))
- # Neither GYP_MSVS_VERSION nor the path help us out. Iterate through
- # the choices looking for a match.
- for version in sorted(possible_paths, reverse=True):
- path = possible_paths[version]
- for r in possible_roots:
- build_tool = os.path.join(r, path)
- if os.path.exists(build_tool):
- uses_msbuild = msvs_version >= '2010'
- msbuild_path = FindMSBuildInstallation(msvs_version)
- return build_tool, uses_msbuild, msbuild_path
- print 'Error: could not find devenv'
- sys.exit(1)
- class TestGypOnMSToolchain(TestGypBase):
- """
- Common subclass for testing generators that target the Microsoft Visual
- Studio toolchain (cl, link, dumpbin, etc.)
- """
- @staticmethod
- def _ComputeVsvarsPath(devenv_path):
- devenv_dir = os.path.split(devenv_path)[0]
- vsvars_path = os.path.join(devenv_path, '../../Tools/vsvars32.bat')
- return vsvars_path
- def initialize_build_tool(self):
- super(TestGypOnMSToolchain, self).initialize_build_tool()
- if sys.platform in ('win32', 'cygwin'):
- build_tools = FindVisualStudioInstallation()
- self.devenv_path, self.uses_msbuild, self.msbuild_path = build_tools
- self.vsvars_path = TestGypOnMSToolchain._ComputeVsvarsPath(
- self.devenv_path)
- def run_dumpbin(self, *dumpbin_args):
- """Run the dumpbin tool with the specified arguments, and capturing and
- returning stdout."""
- assert sys.platform in ('win32', 'cygwin')
- cmd = os.environ.get('COMSPEC', 'cmd.exe')
- arguments = [cmd, '/c', self.vsvars_path, '&&', 'dumpbin']
- arguments.extend(dumpbin_args)
- proc = subprocess.Popen(arguments, stdout=subprocess.PIPE)
- output = proc.communicate()[0]
- assert not proc.returncode
- return output
- class TestGypNinja(TestGypOnMSToolchain):
- """
- Subclass for testing the GYP Ninja generator.
- """
- format = 'ninja'
- build_tool_list = ['ninja']
- ALL = 'all'
- DEFAULT = 'all'
- def run_gyp(self, gyp_file, *args, **kw):
- TestGypBase.run_gyp(self, gyp_file, *args, **kw)
- def build(self, gyp_file, target=None, **kw):
- arguments = kw.get('arguments', [])[:]
- # Add a -C output/path to the command line.
- arguments.append('-C')
- arguments.append(os.path.join('out', self.configuration_dirname()))
- if target is None:
- target = 'all'
- arguments.append(target)
- kw['arguments'] = arguments
- return self.run(program=self.build_tool, **kw)
- def run_built_executable(self, name, *args, **kw):
- # Enclosing the name in a list avoids prepending the original dir.
- program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
- if sys.platform == 'darwin':
- configuration = self.configuration_dirname()
- os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
- return self.run(program=program, *args, **kw)
- def built_file_path(self, name, type=None, **kw):
- result = []
- chdir = kw.get('chdir')
- if chdir:
- result.append(chdir)
- result.append('out')
- result.append(self.configuration_dirname())
- if type == self.STATIC_LIB:
- if sys.platform != 'darwin':
- result.append('obj')
- elif type == self.SHARED_LIB:
- if sys.platform != 'darwin' and sys.platform != 'win32':
- result.append('lib')
- subdir = kw.get('subdir')
- if subdir and type != self.SHARED_LIB:
- result.append(subdir)
- result.append(self.built_file_basename(name, type, **kw))
- return self.workpath(*result)
- def up_to_date(self, gyp_file, target=None, **kw):
- result = self.build(gyp_file, target, **kw)
- if not result:
- stdout = self.stdout()
- if 'ninja: no work to do' not in stdout:
- self.report_not_up_to_date()
- self.fail_test()
- return result
- class TestGypMSVS(TestGypOnMSToolchain):
- """
- Subclass for testing the GYP Visual Studio generator.
- """
- format = 'msvs'
- u = r'=== Build: 0 succeeded, 0 failed, (\d+) up-to-date, 0 skipped ==='
- up_to_date_re = re.compile(u, re.M)
- # Initial None element will indicate to our .initialize_build_tool()
- # method below that 'devenv' was not found on %PATH%.
- #
- # Note: we must use devenv.com to be able to capture build output.
- # Directly executing devenv.exe only sends output to BuildLog.htm.
- build_tool_list = [None, 'devenv.com']
- def initialize_build_tool(self):
- super(TestGypMSVS, self).initialize_build_tool()
- self.build_tool = self.devenv_path
- def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
- """
- Runs a Visual Studio build using the configuration generated
- from the specified gyp_file.
- """
- configuration = self.configuration_buildname()
- if clean:
- build = '/Clean'
- elif rebuild:
- build = '/Rebuild'
- else:
- build = '/Build'
- arguments = kw.get('arguments', [])[:]
- arguments.extend([gyp_file.replace('.gyp', '.sln'),
- build, configuration])
- # Note: the Visual Studio generator doesn't add an explicit 'all'
- # target, so we just treat it the same as the default.
- if target not in (None, self.ALL, self.DEFAULT):
- arguments.extend(['/Project', target])
- if self.configuration:
- arguments.extend(['/ProjectConfig', self.configuration])
- kw['arguments'] = arguments
- return self.run(program=self.build_tool, **kw)
- def up_to_date(self, gyp_file, target=None, **kw):
- """
- Verifies that a build of the specified Visual Studio target is up to date.
- Beware that VS2010 will behave strangely if you build under
- C:\USERS\yourname\AppData\Local. It will cause needless work. The ouptut
- will be "1 succeeded and 0 up to date". MSBuild tracing reveals that:
- "Project 'C:\Users\...\AppData\Local\...vcxproj' not up to date because
- 'C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 10.0\VC\BIN\1033\CLUI.DLL'
- was modified at 02/21/2011 17:03:30, which is newer than '' which was
- modified at 01/01/0001 00:00:00.
- The workaround is to specify a workdir when instantiating the test, e.g.
- test = TestGyp.TestGyp(workdir='workarea')
- """
- result = self.build(gyp_file, target, **kw)
- if not result:
- stdout = self.stdout()
- m = self.up_to_date_re.search(stdout)
- up_to_date = m and int(m.group(1)) > 0
- if not up_to_date:
- self.report_not_up_to_date()
- self.fail_test()
- return result
- def run_built_executable(self, name, *args, **kw):
- """
- Runs an executable built by Visual Studio.
- """
- configuration = self.configuration_dirname()
- # Enclosing the name in a list avoids prepending the original dir.
- program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
- return self.run(program=program, *args, **kw)
- def built_file_path(self, name, type=None, **kw):
- """
- Returns a path to the specified file name, of the specified type,
- as built by Visual Studio.
- Built files are in a subdirectory that matches the configuration
- name. The default is 'Default'.
- A chdir= keyword argument specifies the source directory
- relative to which the output subdirectory can be found.
- "type" values of STATIC_LIB or SHARED_LIB append the necessary
- prefixes and suffixes to a platform-independent library base name.
- """
- result = []
- chdir = kw.get('chdir')
- if chdir:
- result.append(chdir)
- result.append(self.configuration_dirname())
- if type == self.STATIC_LIB:
- result.append('lib')
- result.append(self.built_file_basename(name, type, **kw))
- return self.workpath(*result)
- class TestGypMSVSNinja(TestGypNinja):
- """
- Subclass for testing the GYP Visual Studio Ninja generator.
- """
- format = 'msvs-ninja'
- def initialize_build_tool(self):
- super(TestGypMSVSNinja, self).initialize_build_tool()
- # When using '--build', make sure ninja is first in the format list.
- self.formats.insert(0, 'ninja')
- def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
- """
- Runs a Visual Studio build using the configuration generated
- from the specified gyp_file.
- """
- arguments = kw.get('arguments', [])[:]
- if target in (None, self.ALL, self.DEFAULT):
- # Note: the Visual Studio generator doesn't add an explicit 'all' target.
- # This will build each project. This will work if projects are hermetic,
- # but may fail if they are not (a project may run more than once).
- # It would be nice to supply an all.metaproj for MSBuild.
- arguments.extend([gyp_file.replace('.gyp', '.sln')])
- else:
- # MSBuild documentation claims that one can specify a sln but then build a
- # project target like 'msbuild a.sln /t:proj:target' but this format only
- # supports 'Clean', 'Rebuild', and 'Publish' (with none meaning Default).
- # This limitation is due to the .sln -> .sln.metaproj conversion.
- # The ':' is not special, 'proj:target' is a target in the metaproj.
- arguments.extend([target+'.vcxproj'])
- if clean:
- build = 'Clean'
- elif rebuild:
- build = 'Rebuild'
- else:
- build = 'Build'
- arguments.extend(['/target:'+build])
- configuration = self.configuration_buildname()
- config = configuration.split('|')
- arguments.extend(['/property:Configuration='+config[0]])
- if len(config) > 1:
- arguments.extend(['/property:Platform='+config[1]])
- arguments.extend(['/property:BuildInParallel=false'])
- arguments.extend(['/verbosity:minimal'])
- kw['arguments'] = arguments
- return self.run(program=self.msbuild_path, **kw)
- class TestGypXcode(TestGypBase):
- """
- Subclass for testing the GYP Xcode generator.
- """
- format = 'xcode'
- build_tool_list = ['xcodebuild']
- phase_script_execution = ("\n"
- "PhaseScriptExecution /\\S+/Script-[0-9A-F]+\\.sh\n"
- " cd /\\S+\n"
- " /bin/sh -c /\\S+/Script-[0-9A-F]+\\.sh\n"
- "(make: Nothing to be done for `all'\\.\n)?")
- strip_up_to_date_expressions = [
- # Various actions or rules can run even when the overall build target
- # is up to date. Strip those phases' GYP-generated output.
- re.compile(phase_script_execution, re.S),
- # The message from distcc_pump can trail the "BUILD SUCCEEDED"
- # message, so strip that, too.
- re.compile('__________Shutting down distcc-pump include server\n', re.S),
- ]
- up_to_date_endings = (
- 'Checking Dependencies...\n** BUILD SUCCEEDED **\n', # Xcode 3.0/3.1
- 'Check dependencies\n** BUILD SUCCEEDED **\n\n', # Xcode 3.2
- 'Check dependencies\n\n\n** BUILD SUCCEEDED **\n\n', # Xcode 4.2
- 'Check dependencies\n\n** BUILD SUCCEEDED **\n\n', # Xcode 5.0
- )
- def build(self, gyp_file, target=None, **kw):
- """
- Runs an xcodebuild using the .xcodeproj generated from the specified
- gyp_file.
- """
- # Be sure we're working with a copy of 'arguments' since we modify it.
- # The caller may not be expecting it to be modified.
- arguments = kw.get('arguments', [])[:]
- arguments.extend(['-project', gyp_file.replace('.gyp', '.xcodeproj')])
- if target == self.ALL:
- arguments.append('-alltargets',)
- elif target not in (None, self.DEFAULT):
- arguments.extend(['-target', target])
- if self.configuration:
- arguments.extend(['-configuration', self.configuration])
- symroot = kw.get('SYMROOT', '$SRCROOT/build')
- if symroot:
- arguments.append('SYMROOT='+symroot)
- kw['arguments'] = arguments
- # Work around spurious stderr output from Xcode 4, http://crbug.com/181012
- match = kw.pop('match', self.match)
- def match_filter_xcode(actual, expected):
- if actual:
- if not TestCmd.is_List(actual):
- actual = actual.split('\n')
- if not TestCmd.is_List(expected):
- expected = expected.split('\n')
- actual = [a for a in actual
- if 'No recorder, buildTask: <Xcode3BuildTask:' not in a]
- return match(actual, expected)
- kw['match'] = match_filter_xcode
- return self.run(program=self.build_tool, **kw)
- def up_to_date(self, gyp_file, target=None, **kw):
- """
- Verifies that a build of the specified Xcode target is up to date.
- """
- result = self.build(gyp_file, target, **kw)
- if not result:
- output = self.stdout()
- for expression in self.strip_up_to_date_expressions:
- output = expression.sub('', output)
- if not output.endswith(self.up_to_date_endings):
- self.report_not_up_to_date()
- self.fail_test()
- return result
- def run_built_executable(self, name, *args, **kw):
- """
- Runs an executable built by xcodebuild.
- """
- configuration = self.configuration_dirname()
- os.environ['DYLD_LIBRARY_PATH'] = os.path.join('build', configuration)
- # Enclosing the name in a list avoids prepending the original dir.
- program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
- return self.run(program=program, *args, **kw)
- def built_file_path(self, name, type=None, **kw):
- """
- Returns a path to the specified file name, of the specified type,
- as built by Xcode.
- Built files are in the subdirectory 'build/{configuration}'.
- The default is 'build/Default'.
- A chdir= keyword argument specifies the source directory
- relative to which the output subdirectory can be found.
- "type" values of STATIC_LIB or SHARED_LIB append the necessary
- prefixes and suffixes to a platform-independent library base name.
- """
- result = []
- chdir = kw.get('chdir')
- if chdir:
- result.append(chdir)
- configuration = self.configuration_dirname()
- result.extend(['build', configuration])
- result.append(self.built_file_basename(name, type, **kw))
- return self.workpath(*result)
- class TestGypXcodeNinja(TestGypXcode):
- """
- Subclass for testing the GYP Xcode Ninja generator.
- """
- format = 'xcode-ninja'
- def initialize_build_tool(self):
- super(TestGypXcodeNinja, self).initialize_build_tool()
- # When using '--build', make sure ninja is first in the format list.
- self.formats.insert(0, 'ninja')
- def build(self, gyp_file, target=None, **kw):
- """
- Runs an xcodebuild using the .xcodeproj generated from the specified
- gyp_file.
- """
- build_config = self.configuration
- if build_config and build_config.endswith(('-iphoneos',
- '-iphonesimulator')):
- build_config, sdk = self.configuration.split('-')
- kw['arguments'] = kw.get('arguments', []) + ['-sdk', sdk]
- with self._build_configuration(build_config):
- return super(TestGypXcodeNinja, self).build(
- gyp_file.replace('.gyp', '.ninja.gyp'), target, **kw)
- @contextmanager
- def _build_configuration(self, build_config):
- config = self.configuration
- self.configuration = build_config
- try:
- yield
- finally:
- self.configuration = config
- def built_file_path(self, name, type=None, **kw):
- result = []
- chdir = kw.get('chdir')
- if chdir:
- result.append(chdir)
- result.append('out')
- result.append(self.configuration_dirname())
- subdir = kw.get('subdir')
- if subdir and type != self.SHARED_LIB:
- result.append(subdir)
- result.append(self.built_file_basename(name, type, **kw))
- return self.workpath(*result)
- def up_to_date(self, gyp_file, target=None, **kw):
- result = self.build(gyp_file, target, **kw)
- if not result:
- stdout = self.stdout()
- if 'ninja: no work to do' not in stdout:
- self.report_not_up_to_date()
- self.fail_test()
- return result
- def run_built_executable(self, name, *args, **kw):
- """
- Runs an executable built by xcodebuild + ninja.
- """
- configuration = self.configuration_dirname()
- os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
- # Enclosing the name in a list avoids prepending the original dir.
- program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
- return self.run(program=program, *args, **kw)
- format_class_list = [
- TestGypGypd,
- TestGypAndroid,
- TestGypCMake,
- TestGypMake,
- TestGypMSVS,
- TestGypMSVSNinja,
- TestGypNinja,
- TestGypXcode,
- TestGypXcodeNinja,
- ]
- def TestGyp(*args, **kw):
- """
- Returns an appropriate TestGyp* instance for a specified GYP format.
- """
- format = kw.pop('format', os.environ.get('TESTGYP_FORMAT'))
- for format_class in format_class_list:
- if format == format_class.format:
- return format_class(*args, **kw)
- raise Exception, "unknown format %r" % format
|