Fix msvs-ninja OutputDirectory path.
_FixPath is designed to take gyp paths and convert them to msvs project paths.
This translates a <gyp_dir>"<gyp_dir_to_X>" to
<msvs_project_dir>"<msvs_project_dir_to_gyp_dir>/<gyp_dir_to_X>".
The OutputDirectory when using ninja as the external builder with the ninja
generator generated build files needs to be the path
<msvs_project_dir>"<msvs_project_dir_to_ninja_build_config>".
Since this is specified on a per target basis and will be run though _FixPath,
the external builder directory in the target must be specified as
<gyp_dir>"<gyp_dir_to_toplevel_dir>/<toplevel_dir_to_ninja_build>/<config>".
Chromium currently does not see any issue as it does not set generator_output.
When generator_output is not set, _GetPathOfProject sets fix_prefix to None.
Also, Chromium appears to be using an absolute path for
msvs_external_builder_out_dir.
This is, however, affecting Skia, which sets generator_output to 'out' and
places all of its gyp files in a 'gyp' directory. As a result Skia is seeing
the OutputDirectory set to "../../gyp/out/$(Configuration)". This change fixes
this to "../../out/$(Configuration)".
R=scottmg@chromium.org
Review URL: https://codereview.chromium.org/245923003
git-svn-id: http://gyp.googlecode.com/svn/trunk@1926 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/buildbot/buildbot_run.py b/buildbot/buildbot_run.py
index 979073c..cc3a25a 100755
--- a/buildbot/buildbot_run.py
+++ b/buildbot/buildbot_run.py
@@ -107,7 +107,7 @@
cwd=ANDROID_DIR)
-def GypTestFormat(title, format=None, msvs_version=None):
+def GypTestFormat(title, format=None, msvs_version=None, tests=[]):
"""Run the gyp tests for a given format, emitting annotator tags.
See annotator docs at:
@@ -131,7 +131,7 @@
'--passed',
'--format', format,
'--path', CMAKE_BIN_DIR,
- '--chdir', 'trunk'])
+ '--chdir', 'trunk'] + tests)
if format == 'android':
# gyptest needs the environment setup from envsetup/lunch in order to build
# using the 'android' backend, so this is done in a single shell.
@@ -173,6 +173,12 @@
elif sys.platform == 'win32':
retcode += GypTestFormat('ninja')
if os.environ['BUILDBOT_BUILDERNAME'] == 'gyp-win64':
+ retcode += GypTestFormat('msvs-ninja-2012', format='msvs-ninja',
+ msvs_version='2012',
+ tests=[
+ 'test\generator-output\gyptest-actions.py',
+ 'test\generator-output\gyptest-relocate.py',
+ 'test\generator-output\gyptest-rules.py'])
retcode += GypTestFormat('msvs-2010', format='msvs', msvs_version='2010')
retcode += GypTestFormat('msvs-2012', format='msvs', msvs_version='2012')
else:
diff --git a/pylib/gyp/generator/msvs.py b/pylib/gyp/generator/msvs.py
index 843f97f..a4ebd7c 100644
--- a/pylib/gyp/generator/msvs.py
+++ b/pylib/gyp/generator/msvs.py
@@ -12,6 +12,7 @@
import gyp.common
import gyp.easy_xml as easy_xml
+import gyp.generator.ninja as ninja_generator
import gyp.MSVSNew as MSVSNew
import gyp.MSVSProject as MSVSProject
import gyp.MSVSSettings as MSVSSettings
@@ -1787,7 +1788,7 @@
return projects
-def _InitNinjaFlavor(options, target_list, target_dicts):
+def _InitNinjaFlavor(params, target_list, target_dicts):
"""Initialize targets for the ninja flavor.
This sets up the necessary variables in the targets to generate msvs projects
@@ -1795,7 +1796,7 @@
if they have not been set. This allows individual specs to override the
default values initialized here.
Arguments:
- options: Options provided to the generator.
+ params: Params provided to the generator.
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
"""
@@ -1809,8 +1810,12 @@
spec['msvs_external_builder'] = 'ninja'
if not spec.get('msvs_external_builder_out_dir'):
- spec['msvs_external_builder_out_dir'] = \
- options.depth + '/out/$(Configuration)'
+ gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
+ gyp_dir = os.path.dirname(gyp_file)
+ spec['msvs_external_builder_out_dir'] = os.path.join(
+ gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir),
+ ninja_generator.ComputeOutputDir(params),
+ '$(Configuration)')
if not spec.get('msvs_external_builder_build_cmd'):
spec['msvs_external_builder_build_cmd'] = [
path_to_ninja,
@@ -1905,7 +1910,7 @@
# Optionally configure each spec to use ninja as the external builder.
if params.get('flavor') == 'ninja':
- _InitNinjaFlavor(options, target_list, target_dicts)
+ _InitNinjaFlavor(params, target_list, target_dicts)
# Prepare the set of configurations.
configs = set()
diff --git a/test/ios/gyptest-xcode-ninja.py b/test/ios/gyptest-xcode-ninja.py
index d2ff333..609db8c 100644
--- a/test/ios/gyptest-xcode-ninja.py
+++ b/test/ios/gyptest-xcode-ninja.py
@@ -16,12 +16,8 @@
if sys.platform == 'darwin':
test = TestGyp.TestGyp(formats=['xcode'])
- # Run ninja first
- test.format = 'ninja'
- test.run_gyp('test.gyp', chdir='app-bundle')
-
- # Then run xcode-ninja
- test.format = 'xcode-ninja'
+ # Run ninja and xcode-ninja
+ test.formats = ['ninja', 'xcode-ninja']
test.run_gyp('test.gyp', chdir='app-bundle')
# If it builds the target, it works.
diff --git a/test/lib/TestGyp.py b/test/lib/TestGyp.py
index 36b6281..cde04ca 100644
--- a/test/lib/TestGyp.py
+++ b/test/lib/TestGyp.py
@@ -78,6 +78,7 @@
configuration and to run executables generated by those builds.
"""
+ formats = []
build_tool = None
build_tool_list = []
@@ -113,6 +114,8 @@
self.gyp = os.path.abspath(gyp)
self.no_parallel = False
+ self.formats = [self.format]
+
self.initialize_build_tool()
kw.setdefault('match', TestCommon.match_exact)
@@ -130,10 +133,11 @@
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 ('!'+self.format in excluded_formats or
- included_formats and self.format not in included_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)
@@ -272,9 +276,13 @@
# TODO: --depth=. works around Chromium-specific tree climbing.
depth = kw.pop('depth', '.')
- run_args = ['--depth='+depth, '--format='+self.format, gyp_file]
+ 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)
run_args.extend(args)
return self.run(program=self.gyp, arguments=run_args, **kw)
@@ -747,6 +755,53 @@
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.
@@ -772,39 +827,28 @@
msvs_version = flag.split('=')[-1]
msvs_version = os.environ.get('GYP_MSVS_VERSION', msvs_version)
- build_tool = None
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:
- bt = os.path.join(r, path)
- if os.path.exists(bt):
- build_tool = bt
+ build_tool = os.path.join(r, path)
+ if os.path.exists(build_tool):
uses_msbuild = msvs_version >= '2010'
- return build_tool, uses_msbuild
+ 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))
- if build_tool:
- # We found 'devenv' on the path, use that and try to guess the version.
- for version, path in possible_paths.iteritems():
- if build_tool.find(path) >= 0:
- uses_msbuild = version >= '2010'
- return build_tool, uses_msbuild
- else:
- # If not, assume not MSBuild.
- uses_msbuild = False
- return build_tool, uses_msbuild
# 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:
- bt = os.path.join(r, path)
- if os.path.exists(bt):
- build_tool = bt
+ build_tool = os.path.join(r, path)
+ if os.path.exists(build_tool):
uses_msbuild = msvs_version >= '2010'
- return build_tool, uses_msbuild
+ msbuild_path = FindMSBuildInstallation(msvs_version)
+ return build_tool, uses_msbuild, msbuild_path
print 'Error: could not find devenv'
sys.exit(1)
@@ -822,7 +866,8 @@
def initialize_build_tool(self):
super(TestGypOnMSToolchain, self).initialize_build_tool()
if sys.platform in ('win32', 'cygwin'):
- self.devenv_path, self.uses_msbuild = FindVisualStudioInstallation()
+ build_tools = FindVisualStudioInstallation()
+ self.devenv_path, self.uses_msbuild, self.msbuild_path = build_tools
self.vsvars_path = TestGypOnMSToolchain._ComputeVsvarsPath(
self.devenv_path)
@@ -1002,6 +1047,56 @@
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.
@@ -1118,6 +1213,7 @@
TestGypCMake,
TestGypMake,
TestGypMSVS,
+ TestGypMSVSNinja,
TestGypNinja,
TestGypXcode,
]
diff --git a/test/lib/TestWin.py b/test/lib/TestWin.py
new file mode 100644
index 0000000..1571c85
--- /dev/null
+++ b/test/lib/TestWin.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2014 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.
+
+"""
+TestWin.py: a collection of helpers for testing on Windows.
+"""
+
+import errno
+import os
+import re
+import sys
+import subprocess
+
+class Registry(object):
+ def _QueryBase(self, sysdir, key, value):
+ """Use reg.exe to read a particular key.
+
+ While ideally we might use the win32 module, we would like gyp to be
+ python neutral, so for instance cygwin python lacks this module.
+
+ Arguments:
+ sysdir: The system subdirectory to attempt to launch reg.exe from.
+ key: The registry key to read from.
+ value: The particular value to read.
+ Return:
+ stdout from reg.exe, or None for failure.
+ """
+ # Skip if not on Windows or Python Win32 setup issue
+ if sys.platform not in ('win32', 'cygwin'):
+ return None
+ # Setup params to pass to and attempt to launch reg.exe
+ cmd = [os.path.join(os.environ.get('WINDIR', ''), sysdir, 'reg.exe'),
+ 'query', key]
+ if value:
+ cmd.extend(['/v', value])
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ # Get the stdout from reg.exe, reading to the end so p.returncode is valid
+ # Note that the error text may be in [1] in some cases
+ text = p.communicate()[0]
+ # Check return code from reg.exe; officially 0==success and 1==error
+ if p.returncode:
+ return None
+ return text
+
+ def Query(self, key, value=None):
+ """Use reg.exe to read a particular key through _QueryBase.
+
+ First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
+ that fails, it falls back to System32. Sysnative is available on Vista and
+ up and available on Windows Server 2003 and XP through KB patch 942589. Note
+ that Sysnative will always fail if using 64-bit python due to it being a
+ virtual directory and System32 will work correctly in the first place.
+
+ KB 942589 - http://support.microsoft.com/kb/942589/en-us.
+
+ Arguments:
+ key: The registry key.
+ value: The particular registry value to read (optional).
+ Return:
+ stdout from reg.exe, or None for failure.
+ """
+ text = None
+ try:
+ text = self._QueryBase('Sysnative', key, value)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ text = self._QueryBase('System32', key, value)
+ else:
+ raise
+ return text
+
+ def GetValue(self, key, value):
+ """Use reg.exe to obtain the value of a registry key.
+
+ Args:
+ key: The registry key.
+ value: The particular registry value to read.
+ Return:
+ contents of the registry key's value, or None on failure.
+ """
+ text = self.Query(key, value)
+ if not text:
+ return None
+ # Extract value.
+ match = re.search(r'REG_\w+\s+([^\r]+)\r\n', text)
+ if not match:
+ return None
+ return match.group(1)
+
+ def KeyExists(self, key):
+ """Use reg.exe to see if a key exists.
+
+ Args:
+ key: The registry key to check.
+ Return:
+ True if the key exists
+ """
+ if not self.Query(key):
+ return False
+ return True