зеркало из https://github.com/mozilla/gecko-dev.git
Bug 687388 - Visual Studio project generation; r=mshal
A new moz.build-based build backend for Visual Studio project generation has been added. The build backend can be used by specifying 'VisualStudio' to the backend option of config.status or mach build-backend. e.g. `mach build-backend -b VisualStudio`. Usage docs have been added to build/docs/visualstudio.rst. --HG-- extra : rebase_source : dfb5c43a22434600c5bb4870ec5be2f39b4820e9 extra : amend_source : f1000754400f280778a669b8c34d90c5ef95d966
This commit is contained in:
Родитель
4178316c1c
Коммит
177461528f
|
@ -22,6 +22,7 @@ Important Concepts
|
|||
mozinfo
|
||||
preprocessor
|
||||
jar-manifests
|
||||
visualstudio
|
||||
|
||||
mozbuild
|
||||
========
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
.. _build_visualstudio:
|
||||
|
||||
======================
|
||||
Visual Studio Projects
|
||||
======================
|
||||
|
||||
The build system contains alpha support for generating Visual Studio
|
||||
project files to aid with development.
|
||||
|
||||
To generate Visual Studio project files, you'll need to have a configured tree::
|
||||
|
||||
mach configure
|
||||
|
||||
(If you have built recently, your tree is already configured.)
|
||||
|
||||
Then, simply generate the Visual Studio build backend::
|
||||
|
||||
mach build-backend -b VisualStudio
|
||||
|
||||
If all goes well, the path to the generated Solution (``.sln``) file should be
|
||||
printed. You should be able to open that solution with Visual Studio 2010 or
|
||||
newer.
|
||||
|
||||
Currently, output is hard-coded to the Visual Studio 2010 format. If you open
|
||||
the solution in a newer Visual Studio release, you will be prompted to upgrade
|
||||
projects. Simply click through the wizard to do that.
|
||||
|
||||
Structure of Solution
|
||||
=====================
|
||||
|
||||
The Visual Studio solution consists of hundreds of projects spanning thousands
|
||||
of files. To help with organization, the solution is divided into the following
|
||||
trees/folders:
|
||||
|
||||
Build Targets
|
||||
This folder contains common build targets. The *full* project is used to
|
||||
perform a full build. The *binaries* project is used to build just binaries.
|
||||
The *visual-studio* project can be built to regenerate the Visual Studio
|
||||
project files.
|
||||
|
||||
Performing the *clean* action on any of these targets will clean the
|
||||
*entire* build output.
|
||||
|
||||
Binaries
|
||||
This folder contains common binaries that can be executed from within
|
||||
Visual Studio. If you are building the Firefox desktop application,
|
||||
the *firefox* project will launch firefox.exe. You probably want one of
|
||||
these set to your startup project.
|
||||
|
||||
Libraries
|
||||
This folder contains entries for each static library that is produced as
|
||||
part of the build. These roughly correspond to each directory in the tree
|
||||
containing C/C++. e.g. code from ``dom/base`` will be contained in the
|
||||
``dom_base`` project.
|
||||
|
||||
These projects don't do anything when built. If you build a project here,
|
||||
the *binaries* build target project is built.
|
||||
|
||||
Updating Project Files
|
||||
======================
|
||||
|
||||
As you pull and update the source tree, your Visual Studio files may fall out
|
||||
of sync with the build configuration. The tree should still build fine from
|
||||
within Visual Studio. But source files may be missing and IntelliSense may not
|
||||
have the proper build configuration.
|
||||
|
||||
To account for this, you'll want to periodically regenerate the Visual Studio
|
||||
project files. You can do this within Visual Studio by building the
|
||||
``Build Targets :: visual-studio`` project or by running
|
||||
``mach build-backend -b VisualStudio`` from the command line.
|
||||
|
||||
Currently, regeneration rewrites the original project files. **If you've made
|
||||
any customizations to the solution or projects, they will likely get
|
||||
overwritten.** We would like to improve this user experience in the
|
||||
future.
|
||||
|
||||
Moving Project Files Around
|
||||
===========================
|
||||
|
||||
The produced Visual Studio solution and project files should be portable.
|
||||
If you want to move them to a non-default directory, they should continue
|
||||
to work from wherever they are. If they don't, please file a bug.
|
||||
|
||||
Invoking mach through Visual Studio
|
||||
===================================
|
||||
|
||||
It's possible to build the tree via Visual Studio. There is some light magic
|
||||
involved here.
|
||||
|
||||
Alongside the Visual Studio project files is a batch script named ``mach.bat``.
|
||||
This batch script sets the environment variables present in your *MozillaBuild*
|
||||
development environment at the time of Visual Studio project generation
|
||||
and invokes *mach* inside an msys shell with the arguments specified to the
|
||||
batch script. This script essentially allows you to invoke mach commands
|
||||
inside the MozillaBuild environment without having to load MozillaBuild.
|
||||
|
||||
While projects currently only utilize the ``mach build`` command, the batch
|
||||
script does not limit it's use: any mach command can be invoked. Developers
|
||||
may abuse this fact to add custom projects and commands that invoke other
|
||||
mach commands.
|
|
@ -0,0 +1,552 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This file contains a build backend for generating Visual Studio project
|
||||
# files.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import errno
|
||||
import os
|
||||
import re
|
||||
import types
|
||||
import uuid
|
||||
|
||||
from xml.dom import getDOMImplementation
|
||||
|
||||
from mozpack.files import FileFinder
|
||||
|
||||
from .common import CommonBackend
|
||||
from ..frontend.data import (
|
||||
Defines,
|
||||
LibraryDefinition,
|
||||
LocalInclude,
|
||||
VariablePassthru,
|
||||
)
|
||||
|
||||
|
||||
MSBUILD_NAMESPACE = 'http://schemas.microsoft.com/developer/msbuild/2003'
|
||||
|
||||
def get_id(name):
|
||||
return str(uuid.uuid5(uuid.NAMESPACE_URL, name)).upper()
|
||||
|
||||
# TODO validate mappings are correct. only 2010 confirmed so far
|
||||
def visual_studio_product_to_internal_version(version, solution=False):
|
||||
if solution:
|
||||
if version == '2010':
|
||||
return '11.00'
|
||||
elif version == '2011':
|
||||
return '12.00'
|
||||
elif version == '2012':
|
||||
return '13.00'
|
||||
else:
|
||||
raise Exception('Unknown version seen: %s' % version)
|
||||
else:
|
||||
if version == '2010':
|
||||
return '10.00'
|
||||
elif version == '2011':
|
||||
return '11.00'
|
||||
elif version == '2012':
|
||||
return '12.00'
|
||||
else:
|
||||
raise Exception('Unknown version seen: %s' % version)
|
||||
|
||||
class VisualStudioBackend(CommonBackend):
|
||||
"""Generate Visual Studio project files.
|
||||
|
||||
This backend is used to produce Visual Studio projects and a solution
|
||||
to foster developing Firefox with Visual Studio.
|
||||
|
||||
This backend is currently considered experimental. There are many things
|
||||
not optimal about how it works.
|
||||
|
||||
It's worth noting that lots of file I/O here is not using
|
||||
self._write_file(). That's because we need Windows line endings preserved
|
||||
and self._write_file() currently opens files in text mode, which behaves
|
||||
oddly under MozillaBuild.
|
||||
"""
|
||||
|
||||
def _init(self):
|
||||
CommonBackend._init(self)
|
||||
|
||||
# These should eventually evolve into parameters.
|
||||
self._out_dir = os.path.join(self.environment.topobjdir, 'msvc')
|
||||
# But making this one a parameter requires testing first.
|
||||
self._version = '2010'
|
||||
|
||||
self._paths_to_sources = {}
|
||||
self._paths_to_includes = {}
|
||||
self._paths_to_defines = {}
|
||||
self._paths_to_configs = {}
|
||||
self._libs_to_paths = {}
|
||||
|
||||
def detailed(summary):
|
||||
return 'Generated Visual Studio solution at %s' % (
|
||||
os.path.join(self._out_dir, 'mozilla.sln'))
|
||||
|
||||
self.summary.backend_detailed_summary = types.MethodType(detailed,
|
||||
self.summary)
|
||||
|
||||
def consume_object(self, obj):
|
||||
# Just acknowledge everything.
|
||||
obj.ack()
|
||||
|
||||
reldir = getattr(obj, 'relativedir', None)
|
||||
|
||||
if hasattr(obj, 'config') and reldir not in self._paths_to_configs:
|
||||
self._paths_to_configs[reldir] = obj.config
|
||||
|
||||
if isinstance(obj, VariablePassthru):
|
||||
for k, v in obj.variables.items():
|
||||
if k.endswith('SRCS'):
|
||||
s = self._paths_to_sources.setdefault(reldir, set())
|
||||
s.update(v)
|
||||
|
||||
elif isinstance(obj, LibraryDefinition):
|
||||
self._libs_to_paths[obj.basename] = reldir
|
||||
|
||||
elif isinstance(obj, Defines):
|
||||
self._paths_to_defines.setdefault(reldir, {}).update(obj.defines)
|
||||
|
||||
elif isinstance(obj, LocalInclude):
|
||||
p = obj.path
|
||||
includes = self._paths_to_includes.setdefault(reldir, [])
|
||||
|
||||
if p.startswith('/'):
|
||||
includes.append(os.path.join('$(TopSrcDir)', p[1:]))
|
||||
else:
|
||||
includes.append(os.path.join('$(TopSrcDir)', reldir, p))
|
||||
|
||||
def consume_finished(self):
|
||||
out_dir = self._out_dir
|
||||
try:
|
||||
os.makedirs(out_dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
projects = {}
|
||||
|
||||
for lib, path in sorted(self._libs_to_paths.items()):
|
||||
config = self._paths_to_configs.get(path, None)
|
||||
sources = self._paths_to_sources.get(path, set())
|
||||
sources = set(os.path.join('$(TopSrcDir)', path, s) for s in sources)
|
||||
sources = set(os.path.normpath(s) for s in sources)
|
||||
|
||||
finder = FileFinder(os.path.join(self.environment.topsrcdir, path),
|
||||
find_executables=False)
|
||||
|
||||
headers = [t[0] for t in finder.find('*.h')]
|
||||
headers = [os.path.normpath(os.path.join('$(TopSrcDir)',
|
||||
path, f)) for f in headers]
|
||||
|
||||
includes = [
|
||||
os.path.join('$(TopSrcDir)', path),
|
||||
os.path.join('$(TopObjDir)', path),
|
||||
]
|
||||
includes.extend(self._paths_to_includes.get(path, []))
|
||||
includes.append('$(TopObjDir)\\dist\\include\\nss')
|
||||
includes.append('$(TopObjDir)\\dist\\include')
|
||||
|
||||
for v in ('NSPR_CFLAGS', 'NSS_CFLAGS', 'MOZ_JPEG_CFLAGS',
|
||||
'MOZ_PNG_CFLAGS', 'MOZ_ZLIB_CFLAGS', 'MOZ_PIXMAN_CFLAGS'):
|
||||
if not config:
|
||||
break
|
||||
|
||||
args = config.substs.get(v, '').split()
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
if arg.startswith('-I'):
|
||||
includes.append(os.path.normpath(arg[2:]))
|
||||
|
||||
# Pull in system defaults.
|
||||
includes.append('$(DefaultIncludes)')
|
||||
|
||||
includes = [os.path.normpath(i) for i in includes]
|
||||
|
||||
defines = []
|
||||
for k, v in self._paths_to_defines.get(path, {}).items():
|
||||
if v is True:
|
||||
defines.append(k)
|
||||
else:
|
||||
defines.append('%s=%s' % (k, v))
|
||||
|
||||
basename = 'library_%s' % lib
|
||||
project_id = self._write_vs_project(out_dir, basename, lib,
|
||||
includes=includes,
|
||||
forced_includes=['$(TopObjDir)\\dist\\include\\mozilla-config.h'],
|
||||
defines=defines,
|
||||
headers=headers,
|
||||
sources=sources)
|
||||
|
||||
projects[basename] = (project_id, basename, lib)
|
||||
|
||||
# Generate projects that can be used to build common targets.
|
||||
for target in ('export', 'binaries', 'tools', 'full'):
|
||||
basename = 'target_%s' % target
|
||||
command = '$(SolutionDir)\\mach.bat build'
|
||||
if target != 'full':
|
||||
command += ' %s' % target
|
||||
|
||||
project_id = self._write_vs_project(out_dir, basename, target,
|
||||
build_command=command,
|
||||
clean_command='$(SolutionDir)\\mach.bat build clean')
|
||||
|
||||
projects[basename] = (project_id, basename, target)
|
||||
|
||||
# A project that can be used to regenerate the visual studio projects.
|
||||
basename = 'target_vs'
|
||||
project_id = self._write_vs_project(out_dir, basename, 'visual-studio',
|
||||
build_command='$(SolutionDir)\\mach.bat build-backend -b VisualStudio')
|
||||
projects[basename] = (project_id, basename, 'visual-studio')
|
||||
|
||||
# A project to run the main application binary.
|
||||
app_name = self.environment.substs['MOZ_APP_NAME']
|
||||
basename = 'binary_%s' % app_name
|
||||
project_id = self._write_vs_project(out_dir, basename, app_name,
|
||||
debugger=('$(TopObjDir)\\dist\\bin\\%s.exe' % app_name,
|
||||
'-no-remote'))
|
||||
projects[basename] = (project_id, basename, app_name)
|
||||
|
||||
# Projects to run other common binaries.
|
||||
for app in ['js', 'xpcshell']:
|
||||
basename = 'binary_%s' % app
|
||||
project_id = self._write_vs_project(out_dir, basename, app,
|
||||
debugger=('$(TopObjDir)\\dist\\bin\\%s.exe' % app, ''))
|
||||
projects[basename] = (project_id, basename, app)
|
||||
|
||||
# Write out a shared property file with common variables.
|
||||
props_path = os.path.join(out_dir, 'mozilla.props')
|
||||
with open(props_path, 'wb') as fh:
|
||||
self._write_props(fh)
|
||||
|
||||
# Generate some wrapper scripts that allow us to invoke mach inside
|
||||
# a MozillaBuild-like environment. We currently only use the batch
|
||||
# script. We'd like to use the PowerShell script. However, it seems
|
||||
# to buffer output from within Visual Studio (surely this is
|
||||
# configurable) and the default execution policy of PowerShell doesn't
|
||||
# allow custom scripts to be executed.
|
||||
with open(os.path.join(out_dir, 'mach.bat'), 'wb') as fh:
|
||||
self._write_mach_batch(fh)
|
||||
|
||||
with open(os.path.join(out_dir, 'mach.ps1'), 'wb') as fh:
|
||||
self._write_mach_powershell(fh)
|
||||
|
||||
# Write out a solution file to tie it all together.
|
||||
solution_path = os.path.join(out_dir, 'mozilla.sln')
|
||||
with open(solution_path, 'wb') as fh:
|
||||
self._write_solution(fh, projects)
|
||||
|
||||
def _write_solution(self, fh, projects):
|
||||
version = visual_studio_product_to_internal_version(self._version, True)
|
||||
# This is a Visual C++ Project type.
|
||||
project_type = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942'
|
||||
|
||||
# Visual Studio seems to require this header.
|
||||
fh.write('Microsoft Visual Studio Solution File, Format Version %s\r\n' %
|
||||
version)
|
||||
fh.write('# Visual Studio %s\r\n' % self._version)
|
||||
|
||||
binaries_id = projects['target_binaries'][0]
|
||||
|
||||
# Write out entries for each project.
|
||||
for key in sorted(projects):
|
||||
project_id, basename, name = projects[key]
|
||||
path = '%s.vcxproj' % basename
|
||||
|
||||
fh.write('Project("{%s}") = "%s", "%s", "{%s}"\r\n' % (
|
||||
project_type, name, path, project_id))
|
||||
|
||||
# Make all libraries depend on the binaries target.
|
||||
if key.startswith('library_'):
|
||||
fh.write('\tProjectSection(ProjectDependencies) = postProject\r\n')
|
||||
fh.write('\t\t{%s} = {%s}\r\n' % (binaries_id, binaries_id))
|
||||
fh.write('\tEndProjectSection\r\n')
|
||||
|
||||
fh.write('EndProject\r\n')
|
||||
|
||||
# Write out solution folders for organizing things.
|
||||
|
||||
# This is the UUID you use for solution folders.
|
||||
container_id = '2150E333-8FDC-42A3-9474-1A3956D46DE8'
|
||||
|
||||
def write_container(desc):
|
||||
cid = get_id(desc.encode('utf-8'))
|
||||
fh.write('Project("{%s}") = "%s", "%s", "{%s}"\r\n' % (
|
||||
container_id, desc, desc, cid))
|
||||
fh.write('EndProject\r\n')
|
||||
|
||||
return cid
|
||||
|
||||
library_id = write_container('Libraries')
|
||||
target_id = write_container('Build Targets')
|
||||
binary_id = write_container('Binaries')
|
||||
|
||||
fh.write('Global\r\n')
|
||||
|
||||
# Make every project a member of our one configuration.
|
||||
fh.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n')
|
||||
fh.write('\t\tBuild|Win32 = Build|Win32\r\n')
|
||||
fh.write('\tEndGlobalSection\r\n')
|
||||
|
||||
# Set every project's active configuration to the one configuration and
|
||||
# set up the default build project.
|
||||
fh.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n')
|
||||
for name, project in sorted(projects.items()):
|
||||
fh.write('\t\t{%s}.Build|Win32.ActiveCfg = Build|Win32\r\n' % project[0])
|
||||
|
||||
# Only build the full build target by default.
|
||||
# It's important we don't write multiple entries here because they
|
||||
# conflict!
|
||||
if name == 'target_full':
|
||||
fh.write('\t\t{%s}.Build|Win32.Build.0 = Build|Win32\r\n' % project[0])
|
||||
|
||||
fh.write('\tEndGlobalSection\r\n')
|
||||
|
||||
fh.write('\tGlobalSection(SolutionProperties) = preSolution\r\n')
|
||||
fh.write('\t\tHideSolutionNode = FALSE\r\n')
|
||||
fh.write('\tEndGlobalSection\r\n')
|
||||
|
||||
# Associate projects with containers.
|
||||
fh.write('\tGlobalSection(NestedProjects) = preSolution\r\n')
|
||||
for key in sorted(projects):
|
||||
project_id = projects[key][0]
|
||||
|
||||
if key.startswith('library_'):
|
||||
container_id = library_id
|
||||
elif key.startswith('target_'):
|
||||
container_id = target_id
|
||||
elif key.startswith('binary_'):
|
||||
container_id = binary_id
|
||||
else:
|
||||
raise Exception('Unknown project type: %s' % key)
|
||||
|
||||
fh.write('\t\t{%s} = {%s}\r\n' % (project_id, container_id))
|
||||
fh.write('\tEndGlobalSection\r\n')
|
||||
|
||||
fh.write('EndGlobal\r\n')
|
||||
|
||||
def _write_props(self, fh):
|
||||
impl = getDOMImplementation()
|
||||
doc = impl.createDocument(MSBUILD_NAMESPACE, 'Project', None)
|
||||
|
||||
project = doc.documentElement
|
||||
project.setAttribute('xmlns', MSBUILD_NAMESPACE)
|
||||
project.setAttribute('ToolsVersion', '4.0')
|
||||
|
||||
ig = project.appendChild(doc.createElement('ImportGroup'))
|
||||
ig.setAttribute('Label', 'PropertySheets')
|
||||
|
||||
pg = project.appendChild(doc.createElement('PropertyGroup'))
|
||||
pg.setAttribute('Label', 'UserMacros')
|
||||
|
||||
ig = project.appendChild(doc.createElement('ItemGroup'))
|
||||
|
||||
def add_var(k, v):
|
||||
e = pg.appendChild(doc.createElement(k))
|
||||
e.appendChild(doc.createTextNode(v))
|
||||
|
||||
e = ig.appendChild(doc.createElement('BuildMacro'))
|
||||
e.setAttribute('Include', k)
|
||||
|
||||
e = e.appendChild(doc.createElement('Value'))
|
||||
e.appendChild(doc.createTextNode('$(%s)' % k))
|
||||
|
||||
add_var('TopObjDir', os.path.normpath(self.environment.topobjdir))
|
||||
add_var('TopSrcDir', os.path.normpath(self.environment.topsrcdir))
|
||||
add_var('PYTHON', '$(TopObjDir)\\_virtualenv\\Scripts\\python.exe')
|
||||
add_var('MACH', '$(TopSrcDir)\\mach')
|
||||
|
||||
# From MozillaBuild.
|
||||
add_var('DefaultIncludes', os.environ.get('INCLUDE', ''))
|
||||
|
||||
fh.write(b'\xef\xbb\xbf')
|
||||
doc.writexml(fh, addindent=' ', newl='\r\n')
|
||||
|
||||
def _relevant_environment_variables(self):
|
||||
# Write out the environment variables, presumably coming from
|
||||
# MozillaBuild.
|
||||
for k, v in sorted(os.environ.items()):
|
||||
if not re.match('^[a-zA-Z0-9_]+$', k):
|
||||
continue
|
||||
|
||||
if k in ('OLDPWD', 'PS1'):
|
||||
continue
|
||||
|
||||
if k.startswith('_'):
|
||||
continue
|
||||
|
||||
yield k, v
|
||||
|
||||
yield 'TOPSRCDIR', self.environment.topsrcdir
|
||||
yield 'TOPOBJDIR', self.environment.topobjdir
|
||||
|
||||
def _write_mach_powershell(self, fh):
|
||||
for k, v in self._relevant_environment_variables():
|
||||
fh.write(b'$env:%s = "%s"\r\n' % (k, v))
|
||||
|
||||
relpath = os.path.relpath(self.environment.topsrcdir,
|
||||
self.environment.topobjdir).replace('\\', '/')
|
||||
|
||||
fh.write(b'$bashargs = "%s/mach", "--log-no-times"\r\n' % relpath)
|
||||
fh.write(b'$bashargs = $bashargs + $args\r\n')
|
||||
|
||||
fh.write(b"$expanded = $bashargs -join ' '\r\n")
|
||||
fh.write(b'$procargs = "-c", $expanded\r\n')
|
||||
|
||||
fh.write(b'Start-Process -WorkingDirectory $env:TOPOBJDIR '
|
||||
b'-FilePath $env:MOZILLABUILD\\msys\\bin\\bash '
|
||||
b'-ArgumentList $procargs '
|
||||
b'-Wait -NoNewWindow\r\n')
|
||||
|
||||
def _write_mach_batch(self, fh):
|
||||
"""Write out a batch script that builds the tree.
|
||||
|
||||
The script "bootstraps" into the MozillaBuild environment by setting
|
||||
the environment variables that are active in the current MozillaBuild
|
||||
environment. Then, it builds the tree.
|
||||
"""
|
||||
for k, v in self._relevant_environment_variables():
|
||||
fh.write(b'SET "%s=%s"\r\n' % (k, v))
|
||||
|
||||
fh.write(b'cd %TOPOBJDIR%\r\n')
|
||||
|
||||
# We need to convert Windows-native paths to msys paths. Easiest way is
|
||||
# relative paths, since munging c:\ to /c/ is slightly more
|
||||
# complicated.
|
||||
relpath = os.path.relpath(self.environment.topsrcdir,
|
||||
self.environment.topobjdir).replace('\\', '/')
|
||||
|
||||
# We go through mach because it has the logic for choosing the most
|
||||
# appropriate build tool.
|
||||
fh.write(b'"%%MOZILLABUILD%%\\msys\\bin\\bash" '
|
||||
b'-c "%s/mach --log-no-times %%1 %%2 %%3 %%4 %%5 %%6 %%7"' % relpath)
|
||||
|
||||
def _write_vs_project(self, out_dir, basename, name, **kwargs):
|
||||
root = '%s.vcxproj' % basename
|
||||
with open(os.path.join(out_dir, root), 'wb') as fh:
|
||||
project_id, name = VisualStudioBackend.write_vs_project(fh,
|
||||
self._version, name, **kwargs)
|
||||
|
||||
with open(os.path.join(out_dir, '%s.user' % root), 'w') as fh:
|
||||
fh.write('<?xml version="1.0" encoding="utf-8"?>\r\n')
|
||||
fh.write('<Project ToolsVersion="4.0" xmlns="%s">\r\n' %
|
||||
MSBUILD_NAMESPACE)
|
||||
fh.write('</Project>\r\n')
|
||||
|
||||
return project_id
|
||||
|
||||
@staticmethod
|
||||
def write_vs_project(fh, version, name, includes=[],
|
||||
forced_includes=[], defines=[],
|
||||
build_command=None, clean_command=None,
|
||||
debugger=None, headers=[], sources=[]):
|
||||
|
||||
project_id = get_id(name.encode('utf-8'))
|
||||
|
||||
impl = getDOMImplementation()
|
||||
doc = impl.createDocument(MSBUILD_NAMESPACE, 'Project', None)
|
||||
|
||||
project = doc.documentElement
|
||||
project.setAttribute('DefaultTargets', 'Build')
|
||||
project.setAttribute('ToolsVersion', '4.0')
|
||||
project.setAttribute('xmlns', MSBUILD_NAMESPACE)
|
||||
|
||||
ig = project.appendChild(doc.createElement('ItemGroup'))
|
||||
ig.setAttribute('Label', 'ProjectConfigurations')
|
||||
|
||||
pc = ig.appendChild(doc.createElement('ProjectConfiguration'))
|
||||
pc.setAttribute('Include', 'Build|Win32')
|
||||
|
||||
c = pc.appendChild(doc.createElement('Configuration'))
|
||||
c.appendChild(doc.createTextNode('Build'))
|
||||
|
||||
p = pc.appendChild(doc.createElement('Platform'))
|
||||
p.appendChild(doc.createTextNode('Win32'))
|
||||
|
||||
pg = project.appendChild(doc.createElement('PropertyGroup'))
|
||||
pg.setAttribute('Label', 'Globals')
|
||||
|
||||
n = pg.appendChild(doc.createElement('ProjectName'))
|
||||
n.appendChild(doc.createTextNode(name))
|
||||
|
||||
k = pg.appendChild(doc.createElement('Keyword'))
|
||||
k.appendChild(doc.createTextNode('MakeFileProj'))
|
||||
|
||||
g = pg.appendChild(doc.createElement('ProjectGuid'))
|
||||
g.appendChild(doc.createTextNode('{%s}' % project_id))
|
||||
|
||||
rn = pg.appendChild(doc.createElement('RootNamespace'))
|
||||
rn.appendChild(doc.createTextNode('mozilla'))
|
||||
|
||||
i = project.appendChild(doc.createElement('Import'))
|
||||
i.setAttribute('Project', '$(VCTargetsPath)\\Microsoft.Cpp.Default.props')
|
||||
|
||||
ig = project.appendChild(doc.createElement('ImportGroup'))
|
||||
ig.setAttribute('Label', 'ExtensionTargets')
|
||||
|
||||
ig = project.appendChild(doc.createElement('ImportGroup'))
|
||||
ig.setAttribute('Label', 'ExtensionSettings')
|
||||
|
||||
ig = project.appendChild(doc.createElement('ImportGroup'))
|
||||
ig.setAttribute('Label', 'PropertySheets')
|
||||
i = ig.appendChild(doc.createElement('Import'))
|
||||
i.setAttribute('Project', 'mozilla.props')
|
||||
|
||||
pg = project.appendChild(doc.createElement('PropertyGroup'))
|
||||
pg.setAttribute('Label', 'Configuration')
|
||||
ct = pg.appendChild(doc.createElement('ConfigurationType'))
|
||||
ct.appendChild(doc.createTextNode('Makefile'))
|
||||
|
||||
pg = project.appendChild(doc.createElement('PropertyGroup'))
|
||||
pg.setAttribute('Condition', "'$(Configuration)|$(Platform)'=='Build|Win32'")
|
||||
|
||||
if build_command:
|
||||
n = pg.appendChild(doc.createElement('NMakeBuildCommandLine'))
|
||||
n.appendChild(doc.createTextNode(build_command))
|
||||
|
||||
if clean_command:
|
||||
n = pg.appendChild(doc.createElement('NMakeCleanCommandLine'))
|
||||
n.appendChild(doc.createTextNode(clean_command))
|
||||
|
||||
if includes:
|
||||
n = pg.appendChild(doc.createElement('NMakeIncludeSearchPath'))
|
||||
n.appendChild(doc.createTextNode(';'.join(includes)))
|
||||
|
||||
if forced_includes:
|
||||
n = pg.appendChild(doc.createElement('NMakeForcedIncludes'))
|
||||
n.appendChild(doc.createTextNode(';'.join(forced_includes)))
|
||||
|
||||
if defines:
|
||||
n = pg.appendChild(doc.createElement('NMakePreprocessorDefinitions'))
|
||||
n.appendChild(doc.createTextNode(';'.join(defines)))
|
||||
|
||||
if debugger:
|
||||
n = pg.appendChild(doc.createElement('LocalDebuggerCommand'))
|
||||
n.appendChild(doc.createTextNode(debugger[0]))
|
||||
|
||||
n = pg.appendChild(doc.createElement('LocalDebuggerCommandArguments'))
|
||||
n.appendChild(doc.createTextNode(debugger[1]))
|
||||
|
||||
i = project.appendChild(doc.createElement('Import'))
|
||||
i.setAttribute('Project', '$(VCTargetsPath)\\Microsoft.Cpp.props')
|
||||
|
||||
i = project.appendChild(doc.createElement('Import'))
|
||||
i.setAttribute('Project', '$(VCTargetsPath)\\Microsoft.Cpp.targets')
|
||||
|
||||
# Now add files to the project.
|
||||
ig = project.appendChild(doc.createElement('ItemGroup'))
|
||||
for header in sorted(headers or []):
|
||||
n = ig.appendChild(doc.createElement('ClInclude'))
|
||||
n.setAttribute('Include', header)
|
||||
|
||||
ig = project.appendChild(doc.createElement('ItemGroup'))
|
||||
for source in sorted(sources or []):
|
||||
n = ig.appendChild(doc.createElement('ClCompile'))
|
||||
n.setAttribute('Include', source)
|
||||
|
||||
fh.write(b'\xef\xbb\xbf')
|
||||
doc.writexml(fh, addindent=' ', newl='\r\n')
|
||||
|
||||
return project_id, name
|
|
@ -16,7 +16,6 @@ from optparse import OptionParser
|
|||
|
||||
from mach.logging import LoggingManager
|
||||
from mozbuild.backend.configenvironment import ConfigEnvironment
|
||||
from mozbuild.backend.android_eclipse import AndroidEclipseBackend
|
||||
from mozbuild.backend.recursivemake import RecursiveMakeBackend
|
||||
from mozbuild.base import MachCommandConditions
|
||||
from mozbuild.frontend.emitter import TreeMetadataEmitter
|
||||
|
@ -40,6 +39,19 @@ verify any changes using |mach build|.
|
|||
=============
|
||||
'''.strip()
|
||||
|
||||
VISUAL_STUDIO_ADVERTISEMENT = '''
|
||||
===============================
|
||||
Visual Studio Support Available
|
||||
|
||||
You are building Firefox on Windows. Please help us test the experimental
|
||||
Visual Studio project files (yes, IntelliSense works) by running the
|
||||
following:
|
||||
|
||||
mach build-backend --backend=VisualStudio
|
||||
|
||||
===============================
|
||||
'''.strip()
|
||||
|
||||
|
||||
def config_status(topobjdir='.', topsrcdir='.',
|
||||
defines=[], non_global_defines=[], substs=[], source=None):
|
||||
|
@ -83,7 +95,7 @@ def config_status(topobjdir='.', topsrcdir='.',
|
|||
parser.add_option('-d', '--diff', action='store_true',
|
||||
help='print diffs of changed files.')
|
||||
parser.add_option('-b', '--backend',
|
||||
choices=['RecursiveMake', 'AndroidEclipse'],
|
||||
choices=['RecursiveMake', 'AndroidEclipse', 'VisualStudio'],
|
||||
default='RecursiveMake',
|
||||
help='what backend to build (default: RecursiveMake).')
|
||||
options, args = parser.parse_args()
|
||||
|
@ -103,9 +115,13 @@ def config_status(topobjdir='.', topsrcdir='.',
|
|||
# Make an appropriate backend instance, defaulting to RecursiveMakeBackend.
|
||||
backend_cls = RecursiveMakeBackend
|
||||
if options.backend == 'AndroidEclipse':
|
||||
from mozbuild.backend.android_eclipse import AndroidEclipseBackend
|
||||
if not MachCommandConditions.is_android(env):
|
||||
raise Exception('The Android Eclipse backend is not available with this configuration.')
|
||||
backend_cls = AndroidEclipseBackend
|
||||
elif options.backend == 'VisualStudio':
|
||||
from mozbuild.backend.visualstudio import VisualStudioBackend
|
||||
backend_cls = VisualStudioBackend
|
||||
|
||||
the_backend = backend_cls(env)
|
||||
|
||||
|
@ -133,6 +149,10 @@ def config_status(topobjdir='.', topsrcdir='.',
|
|||
for path, diff in sorted(summary.file_diffs.items()):
|
||||
print(diff)
|
||||
|
||||
# Advertise Visual Studio if appropriate.
|
||||
if os.name == 'nt' and options.backend == 'RecursiveMake':
|
||||
print(VISUAL_STUDIO_ADVERTISEMENT)
|
||||
|
||||
# Advertise Eclipse if it is appropriate.
|
||||
if MachCommandConditions.is_android(env):
|
||||
if options.backend == 'RecursiveMake':
|
||||
|
|
|
@ -531,7 +531,7 @@ class Build(MachCommandBase):
|
|||
# It would be nice to filter the choices below based on
|
||||
# conditions, but that is for another day.
|
||||
@CommandArgument('-b', '--backend',
|
||||
choices=['RecursiveMake', 'AndroidEclipse'],
|
||||
choices=['RecursiveMake', 'AndroidEclipse', 'VisualStudio'],
|
||||
default='RecursiveMake',
|
||||
help='Which backend to build (default: RecursiveMake).')
|
||||
def build_backend(self, backend='RecursiveMake', diff=False):
|
||||
|
|
|
@ -61,6 +61,13 @@ CONFIGS = DefaultOnReadDict({
|
|||
('foo', 'bar baz'),
|
||||
],
|
||||
},
|
||||
'visual-studio': {
|
||||
'defines': [],
|
||||
'non_global_defines': [],
|
||||
'substs': [
|
||||
('MOZ_APP_NAME', 'my_app'),
|
||||
],
|
||||
},
|
||||
}, global_default={
|
||||
'defines': [],
|
||||
'non_global_defines': [],
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
FINAL_LIBRARY = 'test'
|
||||
SOURCES += ['bar.cpp', 'foo.cpp']
|
||||
LOCAL_INCLUDES += ['/includeA/foo']
|
||||
DEFINES['DEFINEFOO'] = True
|
||||
DEFINES['DEFINEBAR'] = 'bar'
|
|
@ -0,0 +1,7 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
add_tier_dir('libs', ['dir1'])
|
||||
|
||||
LIBRARY_NAME = 'test'
|
|
@ -0,0 +1,62 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from xml.dom.minidom import parse
|
||||
import os
|
||||
|
||||
from mozbuild.backend.visualstudio import VisualStudioBackend
|
||||
from mozbuild.test.backend.common import BackendTester
|
||||
|
||||
from mozunit import main
|
||||
|
||||
|
||||
class TestVisualStudioBackend(BackendTester):
|
||||
def test_basic(self):
|
||||
"""Ensure we can consume our stub project."""
|
||||
|
||||
env = self._consume('visual-studio', VisualStudioBackend)
|
||||
|
||||
msvc = os.path.join(env.topobjdir, 'msvc')
|
||||
self.assertTrue(os.path.isdir(msvc))
|
||||
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'mozilla.sln')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'mozilla.props')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'mach.bat')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'binary_my_app.vcxproj')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'target_full.vcxproj')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'library_dir1.vcxproj')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(msvc, 'library_dir1.vcxproj.user')))
|
||||
|
||||
d = parse(os.path.join(msvc, 'library_dir1.vcxproj'))
|
||||
self.assertEqual(d.documentElement.tagName, 'Project')
|
||||
els = d.getElementsByTagName('ClCompile')
|
||||
self.assertEqual(len(els), 2)
|
||||
|
||||
# mozilla-config.h should be explicitly listed as an include.
|
||||
els = d.getElementsByTagName('NMakeForcedIncludes')
|
||||
self.assertEqual(len(els), 1)
|
||||
self.assertEqual(els[0].firstChild.nodeValue,
|
||||
'$(TopObjDir)\\dist\\include\\mozilla-config.h')
|
||||
|
||||
# LOCAL_INCLUDES get added to the include search path.
|
||||
els = d.getElementsByTagName('NMakeIncludeSearchPath')
|
||||
self.assertEqual(len(els), 1)
|
||||
includes = els[0].firstChild.nodeValue.split(';')
|
||||
self.assertIn(os.path.normpath('$(TopSrcDir)/includeA/foo'), includes)
|
||||
self.assertIn(os.path.normpath('$(TopSrcDir)/dir1'), includes)
|
||||
self.assertIn(os.path.normpath('$(TopObjDir)/dir1'), includes)
|
||||
self.assertIn(os.path.normpath('$(TopObjDir)\\dist\\include'), includes)
|
||||
|
||||
# DEFINES get added to the project.
|
||||
els = d.getElementsByTagName('NMakePreprocessorDefinitions')
|
||||
self.assertEqual(len(els), 1)
|
||||
defines = els[0].firstChild.nodeValue.split(';')
|
||||
self.assertIn('DEFINEFOO', defines)
|
||||
self.assertIn('DEFINEBAR=bar', defines)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Загрузка…
Ссылка в новой задаче