summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/backend/visualstudio.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/backend/visualstudio.py')
-rw-r--r--python/mozbuild/mozbuild/backend/visualstudio.py712
1 files changed, 712 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/backend/visualstudio.py b/python/mozbuild/mozbuild/backend/visualstudio.py
new file mode 100644
index 0000000000..b9b30804b8
--- /dev/null
+++ b/python/mozbuild/mozbuild/backend/visualstudio.py
@@ -0,0 +1,712 @@
+# 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.
+
+import errno
+import os
+import re
+import sys
+import uuid
+from pathlib import Path
+from xml.dom import getDOMImplementation
+
+from mozpack.files import FileFinder
+
+from mozbuild.base import ExecutionSummary
+
+from ..frontend.data import (
+ Defines,
+ HostProgram,
+ HostSources,
+ Library,
+ LocalInclude,
+ Program,
+ SandboxedWasmLibrary,
+ Sources,
+ UnifiedSources,
+)
+from .common import CommonBackend
+
+MSBUILD_NAMESPACE = "http://schemas.microsoft.com/developer/msbuild/2003"
+MSNATVIS_NAMESPACE = "http://schemas.microsoft.com/vstudio/debugger/natvis/2010"
+
+
+def get_id(name):
+ if sys.version_info[0] == 2:
+ name = name.encode("utf-8")
+ return str(uuid.uuid5(uuid.NAMESPACE_URL, name)).upper()
+
+
+def visual_studio_product_to_solution_version(version):
+ if version == "2017":
+ return "12.00", "15"
+ elif version == "2019":
+ return "12.00", "16"
+ elif version == "2022":
+ return "12.00", "17"
+ else:
+ raise Exception("Unknown version seen: %s" % version)
+
+
+def visual_studio_product_to_platform_toolset_version(version):
+ if version == "2017":
+ return "v141"
+ elif version == "2019":
+ return "v142"
+ elif version == "2022":
+ return "v143"
+ 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.
+ """
+
+ def _init(self):
+ CommonBackend._init(self)
+
+ # These should eventually evolve into parameters.
+ self._out_dir = os.path.join(self.environment.topobjdir, "msvc")
+ self._projsubdir = "projects"
+
+ self._version = self.environment.substs.get("MSVS_VERSION", "2017")
+
+ self._paths_to_sources = {}
+ self._paths_to_includes = {}
+ self._paths_to_defines = {}
+ self._paths_to_configs = {}
+ self._libs_to_paths = {}
+ self._progs_to_paths = {}
+
+ def summary(self):
+ return ExecutionSummary(
+ "VisualStudio backend executed in {execution_time:.2f}s\n"
+ "Generated Visual Studio solution at {path:s}",
+ execution_time=self._execution_time,
+ path=os.path.join(self._out_dir, "mozilla.sln"),
+ )
+
+ def consume_object(self, obj):
+ reldir = getattr(obj, "relsrcdir", None)
+
+ if hasattr(obj, "config") and reldir not in self._paths_to_configs:
+ self._paths_to_configs[reldir] = obj.config
+
+ if isinstance(obj, Sources):
+ self._add_sources(reldir, obj)
+
+ elif isinstance(obj, HostSources):
+ self._add_sources(reldir, obj)
+
+ elif isinstance(obj, UnifiedSources):
+ # XXX we should be letting CommonBackend.consume_object call this
+ # for us instead.
+ self._process_unified_sources(obj)
+
+ elif isinstance(obj, Library) and not isinstance(obj, SandboxedWasmLibrary):
+ self._libs_to_paths[obj.basename] = reldir
+
+ elif isinstance(obj, Program) or isinstance(obj, HostProgram):
+ self._progs_to_paths[obj.program] = reldir
+
+ elif isinstance(obj, Defines):
+ self._paths_to_defines.setdefault(reldir, {}).update(obj.defines)
+
+ elif isinstance(obj, LocalInclude):
+ includes = self._paths_to_includes.setdefault(reldir, [])
+ includes.append(obj.path.full_path)
+
+ # Just acknowledge everything.
+ return True
+
+ def _add_sources(self, reldir, obj):
+ s = self._paths_to_sources.setdefault(reldir, set())
+ s.update(obj.files)
+
+ def _process_unified_sources(self, obj):
+ reldir = getattr(obj, "relsrcdir", None)
+
+ s = self._paths_to_sources.setdefault(reldir, set())
+ s.update(obj.files)
+
+ def consume_finished(self):
+ out_dir = self._out_dir
+ out_proj_dir = os.path.join(self._out_dir, self._projsubdir)
+
+ projects = self._write_projects_for_sources(
+ self._libs_to_paths, "library", out_proj_dir
+ )
+ projects.update(
+ self._write_projects_for_sources(
+ self._progs_to_paths, "binary", out_proj_dir
+ )
+ )
+
+ # 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_proj_dir,
+ basename,
+ target,
+ build_command=command,
+ clean_command="$(SolutionDir)\\mach.bat clobber",
+ )
+
+ 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_proj_dir,
+ basename,
+ "visual-studio",
+ build_command="$(SolutionDir)\\mach.bat build-backend -b VisualStudio",
+ )
+ projects[basename] = (project_id, basename, "visual-studio")
+
+ # Write out a shared property file with common variables.
+ props_path = os.path.join(out_proj_dir, "mozilla.props")
+ with self._write_file(props_path, readmode="rb") 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 self._write_file(os.path.join(out_dir, "mach.bat"), readmode="rb") as fh:
+ self._write_mach_batch(fh)
+
+ with self._write_file(os.path.join(out_dir, "mach.ps1"), readmode="rb") 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 self._write_file(solution_path, readmode="rb") as fh:
+ self._write_solution(fh, projects)
+
+ def _write_projects_for_sources(self, sources, prefix, out_dir):
+ projects = {}
+ for item, path in sorted(sources.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))
+
+ 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, [])
+
+ 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))
+
+ debugger = None
+ if prefix == "binary":
+ if item.startswith(self.environment.substs["MOZ_APP_NAME"]):
+ app_args = "-no-remote -profile $(TopObjDir)\\tmp\\profile-default"
+ if self.environment.substs.get("MOZ_LAUNCHER_PROCESS", False):
+ app_args += " -wait-for-browser"
+ debugger = ("$(TopObjDir)\\dist\\bin\\%s" % item, app_args)
+ else:
+ debugger = ("$(TopObjDir)\\dist\\bin\\%s" % item, "")
+
+ basename = "%s_%s" % (prefix, item)
+
+ project_id = self._write_vs_project(
+ out_dir,
+ basename,
+ item,
+ includes=includes,
+ forced_includes=["$(TopObjDir)\\dist\\include\\mozilla-config.h"],
+ defines=defines,
+ headers=headers,
+ sources=sources,
+ debugger=debugger,
+ )
+
+ projects[basename] = (project_id, basename, item)
+
+ return projects
+
+ def _write_solution(self, fh, projects):
+ # Visual Studio appears to write out its current version in the
+ # solution file. Instead of trying to figure out what version it will
+ # write, try to parse the version out of the existing file and use it
+ # verbatim.
+ vs_version = None
+ try:
+ with open(fh.name, "rb") as sfh:
+ for line in sfh:
+ if line.startswith(b"VisualStudioVersion = "):
+ vs_version = line.split(b" = ", 1)[1].strip()
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+ format_version, comment_version = visual_studio_product_to_solution_version(
+ self._version
+ )
+ # 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"
+ % format_version
+ )
+ fh.write("# Visual Studio %s\r\n" % comment_version)
+
+ if vs_version:
+ fh.write("VisualStudioVersion = %s\r\n" % vs_version)
+
+ # Corresponds to VS2013.
+ fh.write("MinimumVisualStudioVersion = 12.0.31101.0\r\n")
+
+ binaries_id = projects["target_binaries"][0]
+
+ # Write out entries for each project.
+ for key in sorted(projects):
+ project_id, basename, name = projects[key]
+ path = os.path.join(self._projsubdir, "%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)
+ 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))
+
+ natvis = ig.appendChild(doc.createElement("Natvis"))
+ natvis.setAttribute("Include", "../../../toolkit/library/gecko.natvis")
+
+ 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 _create_natvis_type(
+ self, doc, visualizer, name, displayString, stringView=None
+ ):
+
+ t = visualizer.appendChild(doc.createElement("Type"))
+ t.setAttribute("Name", name)
+
+ ds = t.appendChild(doc.createElement("DisplayString"))
+ ds.appendChild(doc.createTextNode(displayString))
+
+ if stringView is not None:
+ sv = t.appendChild(doc.createElement("DisplayString"))
+ sv.appendChild(doc.createTextNode(stringView))
+
+ def _create_natvis_simple_string_type(self, doc, visualizer, name):
+ self._create_natvis_type(
+ doc, visualizer, name + "<char16_t>", "{mData,su}", "mData,su"
+ )
+ self._create_natvis_type(
+ doc, visualizer, name + "<char>", "{mData,s}", "mData,s"
+ )
+
+ def _create_natvis_string_tuple_type(self, doc, visualizer, chartype, formatstring):
+ t = visualizer.appendChild(doc.createElement("Type"))
+ t.setAttribute("Name", "nsTSubstringTuple<" + chartype + ">")
+
+ ds1 = t.appendChild(doc.createElement("DisplayString"))
+ ds1.setAttribute("Condition", "mHead != nullptr")
+ ds1.appendChild(
+ doc.createTextNode("{mHead,na} {mFragB->mData," + formatstring + "}")
+ )
+
+ ds2 = t.appendChild(doc.createElement("DisplayString"))
+ ds2.setAttribute("Condition", "mHead == nullptr")
+ ds2.appendChild(
+ doc.createTextNode(
+ "{mFragA->mData,"
+ + formatstring
+ + "} {mFragB->mData,"
+ + formatstring
+ + "}"
+ )
+ )
+
+ 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.encode("utf-8"), v.encode("utf-8")))
+
+ relpath = os.path.relpath(
+ self.environment.topsrcdir, self.environment.topobjdir
+ ).replace("\\", "/")
+
+ fh.write(
+ b'$bashargs = "%s/mach", "--log-no-times"\r\n' % relpath.encode("utf-8")
+ )
+ fh.write(b"$bashargs = $bashargs + $args\r\n")
+
+ fh.write(b"$expanded = $bashargs -join ' '\r\n")
+ fh.write(b'$procargs = "-c", $expanded\r\n')
+
+ if (Path(os.environ["MOZILLABUILD"]) / "msys2").exists():
+ bash_path = rb"msys2\usr\bin\bash"
+ else:
+ bash_path = rb"msys\bin\bash"
+
+ fh.write(
+ b"Start-Process -WorkingDirectory $env:TOPOBJDIR "
+ b"-FilePath $env:MOZILLABUILD\\%b "
+ b"-ArgumentList $procargs "
+ b"-Wait -NoNewWindow\r\n" % bash_path
+ )
+
+ 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.encode("utf-8"), v.encode("utf-8")))
+
+ 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("\\", "/")
+
+ if (Path(os.environ["MOZILLABUILD"]) / "msys2").exists():
+ bash_path = rb"msys2\usr\bin\bash"
+ else:
+ bash_path = rb"msys\bin\bash"
+
+ # We go through mach because it has the logic for choosing the most
+ # appropriate build tool.
+ fh.write(
+ b'"%%MOZILLABUILD%%\\%b" '
+ b'-c "%s/mach --log-no-times %%1 %%2 %%3 %%4 %%5 %%6 %%7"'
+ % (bash_path, relpath.encode("utf-8"))
+ )
+
+ def _write_vs_project(self, out_dir, basename, name, **kwargs):
+ root = "%s.vcxproj" % basename
+ project_id = get_id(basename)
+
+ with self._write_file(os.path.join(out_dir, root), readmode="rb") as fh:
+ project_id, name = VisualStudioBackend.write_vs_project(
+ fh, self._version, project_id, name, **kwargs
+ )
+
+ with self._write_file(
+ os.path.join(out_dir, "%s.user" % root), readmode="rb"
+ ) 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,
+ project_id,
+ name,
+ includes=[],
+ forced_includes=[],
+ defines=[],
+ build_command=None,
+ clean_command=None,
+ debugger=None,
+ headers=[],
+ sources=[],
+ ):
+
+ 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"))
+
+ pts = pg.appendChild(doc.createElement("PlatformToolset"))
+ pts.appendChild(
+ doc.createTextNode(
+ visual_studio_product_to_platform_toolset_version(version)
+ )
+ )
+
+ 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]))
+
+ # Sets IntelliSense to use c++17 Language Standard
+ n = pg.appendChild(doc.createElement("AdditionalOptions"))
+ n.appendChild(doc.createTextNode("/std:c++17"))
+
+ 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