~hjd/ubuntu/wily/gyp/debian-merged

« back to all changes in this revision

Viewing changes to test/lib/TestGyp.py

  • Committer: Hans Joachim Desserud
  • Date: 2015-10-31 12:46:59 UTC
  • mfrom: (6.2.6 sid)
  • Revision ID: hans_joachim_desserud-20151031124659-lzxekr6woskh4k0b
Merge latest Debian version

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
TestGyp.py:  a testing framework for GYP integration tests.
7
7
"""
8
8
 
 
9
import collections
 
10
from contextlib import contextmanager
 
11
import itertools
9
12
import os
10
13
import re
11
14
import shutil
12
 
import stat
13
15
import subprocess
14
16
import sys
15
17
import tempfile
22
24
  'TestGyp',
23
25
])
24
26
 
 
27
 
25
28
def remove_debug_line_numbers(contents):
26
29
  """Function to remove the line numbers from the debug output
27
 
  of gyp and thus remove the exremem fragility of the stdout
 
30
  of gyp and thus reduce the extreme fragility of the stdout
28
31
  comparison tests.
29
32
  """
30
33
  lines = contents.splitlines()
35
38
  lines = [len(l) > 3 and ":".join(l[3:]) or l for l in lines]
36
39
  return "\n".join(lines)
37
40
 
 
41
 
38
42
def match_modulo_line_numbers(contents_a, contents_b):
39
43
  """File contents matcher that ignores line numbers."""
40
44
  contents_a = remove_debug_line_numbers(contents_a)
41
45
  contents_b = remove_debug_line_numbers(contents_b)
42
46
  return TestCommon.match_exact(contents_a, contents_b)
43
47
 
 
48
 
 
49
@contextmanager
 
50
def LocalEnv(local_env):
 
51
  """Context manager to provide a local OS environment."""
 
52
  old_env = os.environ.copy()
 
53
  os.environ.update(local_env)
 
54
  try:
 
55
    yield
 
56
  finally:
 
57
    os.environ.clear()
 
58
    os.environ.update(old_env)
 
59
 
 
60
 
44
61
class TestGypBase(TestCommon.TestCommon):
45
62
  """
46
63
  Class for controlling end-to-end tests of gyp generators.
60
77
  configuration and to run executables generated by those builds.
61
78
  """
62
79
 
 
80
  formats = []
63
81
  build_tool = None
64
82
  build_tool_list = []
65
83
 
93
111
        else:
94
112
          gyp = 'gyp'
95
113
    self.gyp = os.path.abspath(gyp)
 
114
    self.no_parallel = False
 
115
 
 
116
    self.formats = [self.format]
96
117
 
97
118
    self.initialize_build_tool()
98
119
 
111
132
 
112
133
    super(TestGypBase, self).__init__(*args, **kw)
113
134
 
 
135
    real_format = self.format.split('-')[-1]
114
136
    excluded_formats = set([f for f in formats if f[0] == '!'])
115
137
    included_formats = set(formats) - excluded_formats
116
 
    if ('!'+self.format in excluded_formats or
117
 
        included_formats and self.format not in included_formats):
 
138
    if ('!'+real_format in excluded_formats or
 
139
        included_formats and real_format not in included_formats):
118
140
      msg = 'Invalid test for %r format; skipping test.\n'
119
141
      self.skip_test(msg % self.format)
120
142
 
128
150
    # Clear $GYP_DEFINES for the same reason.
129
151
    if 'GYP_DEFINES' in os.environ:
130
152
      del os.environ['GYP_DEFINES']
 
153
    # Override the user's language settings, which could
 
154
    # otherwise make the output vary from what is expected.
 
155
    os.environ['LC_ALL'] = 'C'
131
156
 
132
157
  def built_file_must_exist(self, name, type=None, **kw):
133
158
    """
155
180
    """
156
181
    return self.must_not_match(self.built_file_path(name, **kw), contents)
157
182
 
 
183
  def built_file_must_not_contain(self, name, contents, **kw):
 
184
    """
 
185
    Fails the test if the specified built file name contains the specified
 
186
    contents.
 
187
    """
 
188
    return self.must_not_contain(self.built_file_path(name, **kw), contents)
 
189
 
158
190
  def copy_test_configuration(self, source_dir, dest_dir):
159
191
    """
160
192
    Copies the test configuration from the specified source_dir
246
278
 
247
279
    # TODO:  --depth=. works around Chromium-specific tree climbing.
248
280
    depth = kw.pop('depth', '.')
249
 
    run_args = ['--depth='+depth, '--format='+self.format, gyp_file]
 
281
    run_args = ['--depth='+depth]
 
282
    run_args.extend(['--format='+f for f in self.formats]);
 
283
    run_args.append(gyp_file)
 
284
    if self.no_parallel:
 
285
      run_args += ['--no-parallel']
 
286
    # TODO: if extra_args contains a '--build' flag
 
287
    # we really want that to only apply to the last format (self.format).
250
288
    run_args.extend(self.extra_args)
 
289
    # Default xcode_ninja_target_pattern to ^.*$ to fix xcode-ninja tests
 
290
    xcode_ninja_target_pattern = kw.pop('xcode_ninja_target_pattern', '.*')
 
291
    run_args.extend(
 
292
      ['-G', 'xcode_ninja_target_pattern=%s' % xcode_ninja_target_pattern])
251
293
    run_args.extend(args)
252
294
    return self.run(program=self.gyp, arguments=run_args, **kw)
253
295
 
349
391
  internal data structure as pretty-printed Python).
350
392
  """
351
393
  format = 'gypd'
 
394
  def __init__(self, gyp=None, *args, **kw):
 
395
    super(TestGypGypd, self).__init__(*args, **kw)
 
396
    # gypd implies the use of 'golden' files, so parallelizing conflicts as it
 
397
    # causes ordering changes.
 
398
    self.no_parallel = True
352
399
 
353
400
 
354
401
class TestGypCustom(TestGypBase):
361
408
    super(TestGypCustom, self).__init__(*args, **kw)
362
409
 
363
410
 
364
 
class TestGypAndroid(TestGypBase):
365
 
  """
366
 
  Subclass for testing the GYP Android makefile generator. Note that
367
 
  build/envsetup.sh and lunch must have been run before running tests.
368
 
 
369
 
  TODO: This is currently an incomplete implementation. We do not support
370
 
  run_built_executable(), so we pass only tests which do not use this. As a
371
 
  result, support for host targets is not properly tested.
372
 
  """
373
 
  format = 'android'
374
 
 
375
 
  # Note that we can't use mmm as the build tool because ...
376
 
  # - it builds all targets, whereas we need to pass a target
377
 
  # - it is a function, whereas the test runner assumes the build tool is a file
378
 
  # Instead we use make and duplicate the logic from mmm.
379
 
  build_tool_list = ['make']
380
 
 
381
 
  # We use our custom target 'gyp_all_modules', as opposed to the 'all_modules'
382
 
  # target used by mmm, to build only those targets which are part of the gyp
383
 
  # target 'all'.
384
 
  ALL = 'gyp_all_modules'
385
 
 
386
 
  def __init__(self, gyp=None, *args, **kw):
387
 
    # Android requires build and test output to be inside its source tree.
388
 
    # We use the following working directory for the test's source, but the
389
 
    # test's build output still goes to $ANDROID_PRODUCT_OUT.
390
 
    # Note that some tests explicitly set format='gypd' to invoke the gypd
391
 
    # backend. This writes to the source tree, but there's no way around this.
392
 
    kw['workdir'] = os.path.join('/tmp', 'gyptest',
393
 
                                 kw.get('workdir', 'testworkarea'))
394
 
    # We need to remove all gyp outputs from out/. Ths is because some tests
395
 
    # don't have rules to regenerate output, so they will simply re-use stale
396
 
    # output if present. Since the test working directory gets regenerated for
397
 
    # each test run, this can confuse things.
398
 
    # We don't have a list of build outputs because we don't know which
399
 
    # dependent targets were built. Instead we delete all gyp-generated output.
400
 
    # This may be excessive, but should be safe.
401
 
    out_dir = os.environ['ANDROID_PRODUCT_OUT']
402
 
    obj_dir = os.path.join(out_dir, 'obj')
403
 
    shutil.rmtree(os.path.join(obj_dir, 'GYP'), ignore_errors = True)
404
 
    for x in ['EXECUTABLES', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES']:
405
 
      for d in os.listdir(os.path.join(obj_dir, x)):
406
 
        if d.endswith('_gyp_intermediates'):
407
 
          shutil.rmtree(os.path.join(obj_dir, x, d), ignore_errors = True)
408
 
    for x in [os.path.join('obj', 'lib'), os.path.join('system', 'lib')]:
409
 
      for d in os.listdir(os.path.join(out_dir, x)):
410
 
        if d.endswith('_gyp.so'):
411
 
          os.remove(os.path.join(out_dir, x, d))
412
 
 
413
 
    super(TestGypAndroid, self).__init__(*args, **kw)
414
 
 
415
 
  def target_name(self, target):
416
 
    if target == self.ALL:
417
 
      return self.ALL
418
 
    # The default target is 'droid'. However, we want to use our special target
419
 
    # to build only the gyp target 'all'.
420
 
    if target in (None, self.DEFAULT):
421
 
      return self.ALL
422
 
    return target
423
 
 
424
 
  def build(self, gyp_file, target=None, **kw):
425
 
    """
426
 
    Runs a build using the Android makefiles generated from the specified
427
 
    gyp_file. This logic is taken from Android's mmm.
428
 
    """
429
 
    arguments = kw.get('arguments', [])[:]
430
 
    arguments.append(self.target_name(target))
 
411
class TestGypCMake(TestGypBase):
 
412
  """
 
413
  Subclass for testing the GYP CMake generator, using cmake's ninja backend.
 
414
  """
 
415
  format = 'cmake'
 
416
  build_tool_list = ['cmake']
 
417
  ALL = 'all'
 
418
 
 
419
  def cmake_build(self, gyp_file, target=None, **kw):
 
420
    arguments = kw.get('arguments', [])[:]
 
421
 
 
422
    self.build_tool_list = ['cmake']
 
423
    self.initialize_build_tool()
 
424
 
 
425
    chdir = os.path.join(kw.get('chdir', '.'),
 
426
                         'out',
 
427
                         self.configuration_dirname())
 
428
    kw['chdir'] = chdir
 
429
 
 
430
    arguments.append('-G')
 
431
    arguments.append('Ninja')
 
432
 
 
433
    kw['arguments'] = arguments
 
434
 
 
435
    stderr = kw.get('stderr', None)
 
436
    if stderr:
 
437
      kw['stderr'] = stderr.split('$$$')[0]
 
438
 
 
439
    self.run(program=self.build_tool, **kw)
 
440
 
 
441
  def ninja_build(self, gyp_file, target=None, **kw):
 
442
    arguments = kw.get('arguments', [])[:]
 
443
 
 
444
    self.build_tool_list = ['ninja']
 
445
    self.initialize_build_tool()
 
446
 
 
447
    # Add a -C output/path to the command line.
431
448
    arguments.append('-C')
432
 
    arguments.append(os.environ['ANDROID_BUILD_TOP'])
 
449
    arguments.append(os.path.join('out', self.configuration_dirname()))
 
450
 
 
451
    if target not in (None, self.DEFAULT):
 
452
      arguments.append(target)
 
453
 
433
454
    kw['arguments'] = arguments
434
 
    chdir = kw.get('chdir', '')
435
 
    makefile = os.path.join(self.workdir, chdir, 'GypAndroid.mk')
436
 
    os.environ['ONE_SHOT_MAKEFILE'] = makefile
437
 
    result = self.run(program=self.build_tool, **kw)
438
 
    del os.environ['ONE_SHOT_MAKEFILE']
439
 
    return result
440
 
 
441
 
  def android_module(self, group, name, subdir):
442
 
    if subdir:
443
 
      name = '%s_%s' % (subdir, name)
444
 
    if group == 'SHARED_LIBRARIES':
445
 
      name = 'lib_%s' % name
446
 
    return '%s_gyp' % name
447
 
 
448
 
  def intermediates_dir(self, group, module_name):
449
 
    return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', group,
450
 
                        '%s_intermediates' % module_name)
 
455
 
 
456
    stderr = kw.get('stderr', None)
 
457
    if stderr:
 
458
      stderrs = stderr.split('$$$')
 
459
      kw['stderr'] = stderrs[1] if len(stderrs) > 1 else ''
 
460
 
 
461
    return self.run(program=self.build_tool, **kw)
 
462
 
 
463
  def build(self, gyp_file, target=None, status=0, **kw):
 
464
    # Two tools must be run to build, cmake and the ninja.
 
465
    # Allow cmake to succeed when the overall expectation is to fail.
 
466
    if status is None:
 
467
      kw['status'] = None
 
468
    else:
 
469
      if not isinstance(status, collections.Iterable): status = (status,)
 
470
      kw['status'] = list(itertools.chain((0,), status))
 
471
    self.cmake_build(gyp_file, target, **kw)
 
472
    kw['status'] = status
 
473
    self.ninja_build(gyp_file, target, **kw)
 
474
 
 
475
  def run_built_executable(self, name, *args, **kw):
 
476
    # Enclosing the name in a list avoids prepending the original dir.
 
477
    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
 
478
    if sys.platform == 'darwin':
 
479
      configuration = self.configuration_dirname()
 
480
      os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
 
481
    return self.run(program=program, *args, **kw)
451
482
 
452
483
  def built_file_path(self, name, type=None, **kw):
453
 
    """
454
 
    Returns a path to the specified file name, of the specified type,
455
 
    as built by Android. Note that we don't support the configuration
456
 
    parameter.
457
 
    """
458
 
    # Built files are in $ANDROID_PRODUCT_OUT. This requires copying logic from
459
 
    # the Android build system.
460
 
    if type == None:
461
 
      return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', 'GYP',
462
 
                          'shared_intermediates', name)
 
484
    result = []
 
485
    chdir = kw.get('chdir')
 
486
    if chdir:
 
487
      result.append(chdir)
 
488
    result.append('out')
 
489
    result.append(self.configuration_dirname())
 
490
    if type == self.STATIC_LIB:
 
491
      if sys.platform != 'darwin':
 
492
        result.append('obj.target')
 
493
    elif type == self.SHARED_LIB:
 
494
      if sys.platform != 'darwin' and sys.platform != 'win32':
 
495
        result.append('lib.target')
463
496
    subdir = kw.get('subdir')
464
 
    if type == self.EXECUTABLE:
465
 
      # We don't install executables
466
 
      group = 'EXECUTABLES'
467
 
      module_name = self.android_module(group, name, subdir)
468
 
      return os.path.join(self.intermediates_dir(group, module_name), name)
469
 
    if type == self.STATIC_LIB:
470
 
      group = 'STATIC_LIBRARIES'
471
 
      module_name = self.android_module(group, name, subdir)
472
 
      return os.path.join(self.intermediates_dir(group, module_name),
473
 
                          '%s.a' % module_name)
474
 
    if type == self.SHARED_LIB:
475
 
      group = 'SHARED_LIBRARIES'
476
 
      module_name = self.android_module(group, name, subdir)
477
 
      return os.path.join(self.intermediates_dir(group, module_name), 'LINKED',
478
 
                          '%s.so' % module_name)
479
 
    assert False, 'Unhandled type'
480
 
 
481
 
  def run_built_executable(self, name, *args, **kw):
482
 
    """
483
 
    Runs an executable program built from a gyp-generated configuration.
484
 
 
485
 
    This is not correctly implemented for Android. For now, we simply check
486
 
    that the executable file exists.
487
 
    """
488
 
    # Running executables requires a device. Even if we build for target x86,
489
 
    # the binary is not built with the correct toolchain options to actually
490
 
    # run on the host.
491
 
 
492
 
    # Copied from TestCommon.run()
493
 
    match = kw.pop('match', self.match)
494
 
    status = None
495
 
    if os.path.exists(self.built_file_path(name)):
496
 
      status = 1
497
 
    self._complete(None, None, None, None, status, match)
498
 
 
499
 
  def match_single_line(self, lines = None, expected_line = None):
500
 
    """
501
 
    Checks that specified line appears in the text.
502
 
    """
503
 
    for line in lines.split('\n'):
504
 
        if line == expected_line:
505
 
            return 1
506
 
    return
 
497
    if subdir and type != self.SHARED_LIB:
 
498
      result.append(subdir)
 
499
    result.append(self.built_file_basename(name, type, **kw))
 
500
    return self.workpath(*result)
507
501
 
508
502
  def up_to_date(self, gyp_file, target=None, **kw):
509
 
    """
510
 
    Verifies that a build of the specified target is up to date.
511
 
    """
512
 
    kw['stdout'] = ("make: Nothing to be done for `%s'." %
513
 
                    self.target_name(target))
 
503
    result = self.ninja_build(gyp_file, target, **kw)
 
504
    if not result:
 
505
      stdout = self.stdout()
 
506
      if 'ninja: no work to do' not in stdout:
 
507
        self.report_not_up_to_date()
 
508
        self.fail_test()
 
509
    return result
514
510
 
515
 
    # We need to supply a custom matcher, since we don't want to depend on the
516
 
    # exact stdout string.
517
 
    kw['match'] = self.match_single_line
518
 
    return self.build(gyp_file, target, **kw)
519
511
 
520
512
class TestGypMake(TestGypBase):
521
513
  """
612
604
  return path
613
605
 
614
606
 
 
607
def FindMSBuildInstallation(msvs_version = 'auto'):
 
608
  """Returns path to MSBuild for msvs_version or latest available.
 
609
 
 
610
  Looks in the registry to find install location of MSBuild.
 
611
  MSBuild before v4.0 will not build c++ projects, so only use newer versions.
 
612
  """
 
613
  import TestWin
 
614
  registry = TestWin.Registry()
 
615
 
 
616
  msvs_to_msbuild = {
 
617
      '2013': r'12.0',
 
618
      '2012': r'4.0',  # Really v4.0.30319 which comes with .NET 4.5.
 
619
      '2010': r'4.0'}
 
620
 
 
621
  msbuild_basekey = r'HKLM\SOFTWARE\Microsoft\MSBuild\ToolsVersions'
 
622
  if not registry.KeyExists(msbuild_basekey):
 
623
    print 'Error: could not find MSBuild base registry entry'
 
624
    return None
 
625
 
 
626
  msbuild_version = None
 
627
  if msvs_version in msvs_to_msbuild:
 
628
    msbuild_test_version = msvs_to_msbuild[msvs_version]
 
629
    if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
 
630
      msbuild_version = msbuild_test_version
 
631
    else:
 
632
      print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
 
633
             'but corresponding MSBuild "%s" was not found.' %
 
634
             (msvs_version, msbuild_version))
 
635
  if not msbuild_version:
 
636
    for msvs_version in sorted(msvs_to_msbuild, reverse=True):
 
637
      msbuild_test_version = msvs_to_msbuild[msvs_version]
 
638
      if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
 
639
        msbuild_version = msbuild_test_version
 
640
        break
 
641
  if not msbuild_version:
 
642
    print 'Error: could not find MSBuild registry entry'
 
643
    return None
 
644
 
 
645
  msbuild_path = registry.GetValue(msbuild_basekey + '\\' + msbuild_version,
 
646
                                   'MSBuildToolsPath')
 
647
  if not msbuild_path:
 
648
    print 'Error: could not get MSBuild registry entry value'
 
649
    return None
 
650
 
 
651
  return os.path.join(msbuild_path, 'MSBuild.exe')
 
652
 
 
653
 
615
654
def FindVisualStudioInstallation():
616
655
  """Returns appropriate values for .build_tool and .uses_msbuild fields
617
656
  of TestGypBase for Visual Studio.
624
663
                    for drive in range(ord('C'), ord('Z') + 1)
625
664
                    for suffix in ['', ' (x86)']]
626
665
  possible_paths = {
 
666
      '2013': r'Microsoft Visual Studio 12.0\Common7\IDE\devenv.com',
627
667
      '2012': r'Microsoft Visual Studio 11.0\Common7\IDE\devenv.com',
628
668
      '2010': r'Microsoft Visual Studio 10.0\Common7\IDE\devenv.com',
629
669
      '2008': r'Microsoft Visual Studio 9.0\Common7\IDE\devenv.com',
636
676
    msvs_version = flag.split('=')[-1]
637
677
  msvs_version = os.environ.get('GYP_MSVS_VERSION', msvs_version)
638
678
 
639
 
  build_tool = None
640
679
  if msvs_version in possible_paths:
641
680
    # Check that the path to the specified GYP_MSVS_VERSION exists.
642
681
    path = possible_paths[msvs_version]
643
682
    for r in possible_roots:
644
 
      bt = os.path.join(r, path)
645
 
      if os.path.exists(bt):
646
 
        build_tool = bt
 
683
      build_tool = os.path.join(r, path)
 
684
      if os.path.exists(build_tool):
647
685
        uses_msbuild = msvs_version >= '2010'
648
 
        return build_tool, uses_msbuild
 
686
        msbuild_path = FindMSBuildInstallation(msvs_version)
 
687
        return build_tool, uses_msbuild, msbuild_path
649
688
    else:
650
689
      print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
651
690
              'but corresponding "%s" was not found.' % (msvs_version, path))
652
 
  if build_tool:
653
 
    # We found 'devenv' on the path, use that and try to guess the version.
654
 
    for version, path in possible_paths.iteritems():
655
 
      if build_tool.find(path) >= 0:
656
 
        uses_msbuild = version >= '2010'
657
 
        return build_tool, uses_msbuild
658
 
    else:
659
 
      # If not, assume not MSBuild.
660
 
      uses_msbuild = False
661
 
    return build_tool, uses_msbuild
662
691
  # Neither GYP_MSVS_VERSION nor the path help us out.  Iterate through
663
692
  # the choices looking for a match.
664
693
  for version in sorted(possible_paths, reverse=True):
665
694
    path = possible_paths[version]
666
695
    for r in possible_roots:
667
 
      bt = os.path.join(r, path)
668
 
      if os.path.exists(bt):
669
 
        build_tool = bt
 
696
      build_tool = os.path.join(r, path)
 
697
      if os.path.exists(build_tool):
670
698
        uses_msbuild = msvs_version >= '2010'
671
 
        return build_tool, uses_msbuild
 
699
        msbuild_path = FindMSBuildInstallation(msvs_version)
 
700
        return build_tool, uses_msbuild, msbuild_path
672
701
  print 'Error: could not find devenv'
673
702
  sys.exit(1)
674
703
 
686
715
  def initialize_build_tool(self):
687
716
    super(TestGypOnMSToolchain, self).initialize_build_tool()
688
717
    if sys.platform in ('win32', 'cygwin'):
689
 
      self.devenv_path, self.uses_msbuild = FindVisualStudioInstallation()
 
718
      build_tools = FindVisualStudioInstallation()
 
719
      self.devenv_path, self.uses_msbuild, self.msbuild_path = build_tools
690
720
      self.vsvars_path = TestGypOnMSToolchain._ComputeVsvarsPath(
691
721
          self.devenv_path)
692
722
 
866
896
    return self.workpath(*result)
867
897
 
868
898
 
 
899
class TestGypMSVSNinja(TestGypNinja):
 
900
  """
 
901
  Subclass for testing the GYP Visual Studio Ninja generator.
 
902
  """
 
903
  format = 'msvs-ninja'
 
904
 
 
905
  def initialize_build_tool(self):
 
906
    super(TestGypMSVSNinja, self).initialize_build_tool()
 
907
    # When using '--build', make sure ninja is first in the format list.
 
908
    self.formats.insert(0, 'ninja')
 
909
 
 
910
  def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
 
911
    """
 
912
    Runs a Visual Studio build using the configuration generated
 
913
    from the specified gyp_file.
 
914
    """
 
915
    arguments = kw.get('arguments', [])[:]
 
916
    if target in (None, self.ALL, self.DEFAULT):
 
917
      # Note: the Visual Studio generator doesn't add an explicit 'all' target.
 
918
      # This will build each project. This will work if projects are hermetic,
 
919
      # but may fail if they are not (a project may run more than once).
 
920
      # It would be nice to supply an all.metaproj for MSBuild.
 
921
      arguments.extend([gyp_file.replace('.gyp', '.sln')])
 
922
    else:
 
923
      # MSBuild documentation claims that one can specify a sln but then build a
 
924
      # project target like 'msbuild a.sln /t:proj:target' but this format only
 
925
      # supports 'Clean', 'Rebuild', and 'Publish' (with none meaning Default).
 
926
      # This limitation is due to the .sln -> .sln.metaproj conversion.
 
927
      # The ':' is not special, 'proj:target' is a target in the metaproj.
 
928
      arguments.extend([target+'.vcxproj'])
 
929
 
 
930
    if clean:
 
931
      build = 'Clean'
 
932
    elif rebuild:
 
933
      build = 'Rebuild'
 
934
    else:
 
935
      build = 'Build'
 
936
    arguments.extend(['/target:'+build])
 
937
    configuration = self.configuration_buildname()
 
938
    config = configuration.split('|')
 
939
    arguments.extend(['/property:Configuration='+config[0]])
 
940
    if len(config) > 1:
 
941
      arguments.extend(['/property:Platform='+config[1]])
 
942
    arguments.extend(['/property:BuildInParallel=false'])
 
943
    arguments.extend(['/verbosity:minimal'])
 
944
 
 
945
    kw['arguments'] = arguments
 
946
    return self.run(program=self.msbuild_path, **kw)
 
947
 
 
948
 
869
949
class TestGypXcode(TestGypBase):
870
950
  """
871
951
  Subclass for testing the GYP Xcode generator.
893
973
    'Checking Dependencies...\n** BUILD SUCCEEDED **\n', # Xcode 3.0/3.1
894
974
    'Check dependencies\n** BUILD SUCCEEDED **\n\n',     # Xcode 3.2
895
975
    'Check dependencies\n\n\n** BUILD SUCCEEDED **\n\n', # Xcode 4.2
 
976
    'Check dependencies\n\n** BUILD SUCCEEDED **\n\n',   # Xcode 5.0
896
977
  )
897
978
 
898
979
  def build(self, gyp_file, target=None, **kw):
975
1056
    return self.workpath(*result)
976
1057
 
977
1058
 
 
1059
class TestGypXcodeNinja(TestGypXcode):
 
1060
  """
 
1061
  Subclass for testing the GYP Xcode Ninja generator.
 
1062
  """
 
1063
  format = 'xcode-ninja'
 
1064
 
 
1065
  def initialize_build_tool(self):
 
1066
    super(TestGypXcodeNinja, self).initialize_build_tool()
 
1067
    # When using '--build', make sure ninja is first in the format list.
 
1068
    self.formats.insert(0, 'ninja')
 
1069
 
 
1070
  def build(self, gyp_file, target=None, **kw):
 
1071
    """
 
1072
    Runs an xcodebuild using the .xcodeproj generated from the specified
 
1073
    gyp_file.
 
1074
    """
 
1075
    build_config = self.configuration
 
1076
    if build_config and build_config.endswith(('-iphoneos',
 
1077
                                               '-iphonesimulator')):
 
1078
      build_config, sdk = self.configuration.split('-')
 
1079
      kw['arguments'] = kw.get('arguments', []) + ['-sdk', sdk]
 
1080
 
 
1081
    with self._build_configuration(build_config):
 
1082
      return super(TestGypXcodeNinja, self).build(
 
1083
        gyp_file.replace('.gyp', '.ninja.gyp'), target, **kw)
 
1084
 
 
1085
  @contextmanager
 
1086
  def _build_configuration(self, build_config):
 
1087
    config = self.configuration
 
1088
    self.configuration = build_config
 
1089
    try:
 
1090
      yield
 
1091
    finally:
 
1092
      self.configuration = config
 
1093
 
 
1094
  def built_file_path(self, name, type=None, **kw):
 
1095
    result = []
 
1096
    chdir = kw.get('chdir')
 
1097
    if chdir:
 
1098
      result.append(chdir)
 
1099
    result.append('out')
 
1100
    result.append(self.configuration_dirname())
 
1101
    subdir = kw.get('subdir')
 
1102
    if subdir and type != self.SHARED_LIB:
 
1103
      result.append(subdir)
 
1104
    result.append(self.built_file_basename(name, type, **kw))
 
1105
    return self.workpath(*result)
 
1106
 
 
1107
  def up_to_date(self, gyp_file, target=None, **kw):
 
1108
    result = self.build(gyp_file, target, **kw)
 
1109
    if not result:
 
1110
      stdout = self.stdout()
 
1111
      if 'ninja: no work to do' not in stdout:
 
1112
        self.report_not_up_to_date()
 
1113
        self.fail_test()
 
1114
    return result
 
1115
 
 
1116
  def run_built_executable(self, name, *args, **kw):
 
1117
    """
 
1118
    Runs an executable built by xcodebuild + ninja.
 
1119
    """
 
1120
    configuration = self.configuration_dirname()
 
1121
    os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration)
 
1122
    # Enclosing the name in a list avoids prepending the original dir.
 
1123
    program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)]
 
1124
    return self.run(program=program, *args, **kw)
 
1125
 
 
1126
 
978
1127
format_class_list = [
979
1128
  TestGypGypd,
980
 
  TestGypAndroid,
 
1129
  TestGypCMake,
981
1130
  TestGypMake,
982
1131
  TestGypMSVS,
 
1132
  TestGypMSVSNinja,
983
1133
  TestGypNinja,
984
1134
  TestGypXcode,
 
1135
  TestGypXcodeNinja,
985
1136
]
986
1137
 
987
1138
def TestGyp(*args, **kw):