| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2014-2016 The Meson development team |
| |
| from __future__ import annotations |
| import copy |
| import itertools |
| import os |
| import xml.dom.minidom |
| import xml.etree.ElementTree as ET |
| import uuid |
| import typing as T |
| from pathlib import Path, PurePath, PureWindowsPath |
| import re |
| from collections import Counter |
| |
| from . import backends |
| from .. import build |
| from .. import mlog |
| from .. import compilers |
| from .. import mesonlib |
| from ..mesonlib import ( |
| File, MesonBugException, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice |
| ) |
| from ..environment import Environment, build_filename |
| from .. import coredata |
| |
| if T.TYPE_CHECKING: |
| from ..arglist import CompilerArgs |
| from ..interpreter import Interpreter |
| |
| Project = T.Tuple[str, Path, str, MachineChoice] |
| |
| def autodetect_vs_version(build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]) -> backends.Backend: |
| vs_version = os.getenv('VisualStudioVersion', None) |
| vs_install_dir = os.getenv('VSINSTALLDIR', None) |
| if not vs_install_dir: |
| raise MesonException('Could not detect Visual Studio: Environment variable VSINSTALLDIR is not set!\n' |
| 'Are you running meson from the Visual Studio Developer Command Prompt?') |
| # VisualStudioVersion is set since Visual Studio 11.0, but sometimes |
| # vcvarsall.bat doesn't set it, so also use VSINSTALLDIR |
| if vs_version == '11.0' or 'Visual Studio 11' in vs_install_dir: |
| from mesonbuild.backend.vs2012backend import Vs2012Backend |
| return Vs2012Backend(build, interpreter) |
| if vs_version == '12.0' or 'Visual Studio 12' in vs_install_dir: |
| from mesonbuild.backend.vs2013backend import Vs2013Backend |
| return Vs2013Backend(build, interpreter) |
| if vs_version == '14.0' or 'Visual Studio 14' in vs_install_dir: |
| from mesonbuild.backend.vs2015backend import Vs2015Backend |
| return Vs2015Backend(build, interpreter) |
| if vs_version == '15.0' or 'Visual Studio 17' in vs_install_dir or \ |
| 'Visual Studio\\2017' in vs_install_dir: |
| from mesonbuild.backend.vs2017backend import Vs2017Backend |
| return Vs2017Backend(build, interpreter) |
| if vs_version == '16.0' or 'Visual Studio 19' in vs_install_dir or \ |
| 'Visual Studio\\2019' in vs_install_dir: |
| from mesonbuild.backend.vs2019backend import Vs2019Backend |
| return Vs2019Backend(build, interpreter) |
| if vs_version == '17.0' or 'Visual Studio 22' in vs_install_dir or \ |
| 'Visual Studio\\2022' in vs_install_dir: |
| from mesonbuild.backend.vs2022backend import Vs2022Backend |
| return Vs2022Backend(build, interpreter) |
| if 'Visual Studio 10.0' in vs_install_dir: |
| return Vs2010Backend(build, interpreter) |
| raise MesonException('Could not detect Visual Studio using VisualStudioVersion: {!r} or VSINSTALLDIR: {!r}!\n' |
| 'Please specify the exact backend to use.'.format(vs_version, vs_install_dir)) |
| |
| |
| def split_o_flags_args(args: T.List[str]) -> T.List[str]: |
| """ |
| Splits any /O args and returns them. Does not take care of flags overriding |
| previous ones. Skips non-O flag arguments. |
| |
| ['/Ox', '/Ob1'] returns ['/Ox', '/Ob1'] |
| ['/Oxj', '/MP'] returns ['/Ox', '/Oj'] |
| """ |
| o_flags = [] |
| for arg in args: |
| if not arg.startswith('/O'): |
| continue |
| flags = list(arg[2:]) |
| # Assume that this one can't be clumped with the others since it takes |
| # an argument itself |
| if 'b' in flags: |
| o_flags.append(arg) |
| else: |
| o_flags += ['/O' + f for f in flags] |
| return o_flags |
| |
| def generate_guid_from_path(path, path_type) -> str: |
| return str(uuid.uuid5(uuid.NAMESPACE_URL, 'meson-vs-' + path_type + ':' + str(path))).upper() |
| |
| def detect_microsoft_gdk(platform: str) -> bool: |
| return re.match(r'Gaming\.(Desktop|Xbox.XboxOne|Xbox.Scarlett)\.x64', platform, re.IGNORECASE) |
| |
| def filtered_src_langs_generator(sources: T.List[str]): |
| for src in sources: |
| ext = src.split('.')[-1] |
| if compilers.compilers.is_source_suffix(ext): |
| yield compilers.compilers.SUFFIX_TO_LANG[ext] |
| |
| # Returns the source language (i.e. a key from 'lang_suffixes') of the most frequent source language in the given |
| # list of sources. |
| # We choose the most frequent language as 'primary' because it means the most sources in a target/project can |
| # simply refer to the project's shared intellisense define and include fields, rather than have to fill out their |
| # own duplicate full set of defines/includes/opts intellisense fields. All of which helps keep the vcxproj file |
| # size down. |
| def get_primary_source_lang(target_sources: T.List[File], custom_sources: T.List[str]) -> T.Optional[str]: |
| lang_counts = Counter([compilers.compilers.SUFFIX_TO_LANG[src.suffix] for src in target_sources if compilers.compilers.is_source_suffix(src.suffix)]) |
| lang_counts += Counter(filtered_src_langs_generator(custom_sources)) |
| most_common_lang_list = lang_counts.most_common(1) |
| # It may be possible that we have a target with no actual src files of interest (e.g. a generator target), |
| # leaving us with an empty list, which we should handle - |
| return most_common_lang_list[0][0] if most_common_lang_list else None |
| |
| # Returns a dictionary (by [src type][build type]) that contains a tuple of - |
| # (pre-processor defines, include paths, additional compiler options) |
| # fields to use to fill in the respective intellisense fields of sources that can't simply |
| # reference and re-use the shared 'primary' language intellisense fields of the vcxproj. |
| def get_non_primary_lang_intellisense_fields(vslite_ctx: dict, |
| target_id: str, |
| primary_src_lang: str) -> T.Dict[str, T.Dict[str, T.Tuple[str, str, str]]]: |
| defs_paths_opts_per_lang_and_buildtype = {} |
| for buildtype in coredata.get_genvs_default_buildtype_list(): |
| captured_build_args = vslite_ctx[buildtype][target_id] # Results in a 'Src types to compile args' dict |
| non_primary_build_args_per_src_lang = [(lang, build_args) for lang, build_args in captured_build_args.items() if lang != primary_src_lang] # Only need to individually populate intellisense fields for sources of non-primary types. |
| for src_lang, args_list in non_primary_build_args_per_src_lang: |
| if src_lang not in defs_paths_opts_per_lang_and_buildtype: |
| defs_paths_opts_per_lang_and_buildtype[src_lang] = {} |
| defs_paths_opts_per_lang_and_buildtype[src_lang][buildtype] = Vs2010Backend._extract_nmake_fields(args_list) |
| return defs_paths_opts_per_lang_and_buildtype |
| |
| class Vs2010Backend(backends.Backend): |
| |
| name = 'vs2010' |
| |
| def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False): |
| super().__init__(build, interpreter) |
| self.project_file_version = '10.0.30319.1' |
| self.sln_file_version = '11.00' |
| self.sln_version_comment = '2010' |
| self.platform_toolset = None |
| self.vs_version = '2010' |
| self.windows_target_platform_version = None |
| self.subdirs = {} |
| self.handled_target_deps = {} |
| self.gen_lite = gen_lite # Synonymous with generating the simpler makefile-style multi-config projects that invoke 'meson compile' builds, avoiding native MSBuild complications |
| |
| def get_target_private_dir(self, target): |
| return os.path.join(self.get_target_dir(target), target.get_id()) |
| |
| def generate_genlist_for_target(self, genlist: T.Union[build.GeneratedList, build.CustomTarget, build.CustomTargetIndex], target: build.BuildTarget, parent_node: ET.Element, generator_output_files: T.List[str], custom_target_include_dirs: T.List[str], custom_target_output_files: T.List[str]) -> None: |
| if isinstance(genlist, build.GeneratedList): |
| for x in genlist.depends: |
| self.generate_genlist_for_target(x, target, parent_node, [], [], []) |
| target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) |
| down = self.target_to_build_root(target) |
| if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)): |
| for i in genlist.get_outputs(): |
| # Path to the generated source from the current vcxproj dir via the build root |
| ipath = os.path.join(down, self.get_target_dir(genlist), i) |
| custom_target_output_files.append(ipath) |
| idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target)) |
| if idir not in custom_target_include_dirs: |
| custom_target_include_dirs.append(idir) |
| else: |
| generator = genlist.get_generator() |
| exe = generator.get_exe() |
| infilelist = genlist.get_inputs() |
| outfilelist = genlist.get_outputs() |
| source_dir = os.path.join(down, self.build_to_src, genlist.subdir) |
| idgroup = ET.SubElement(parent_node, 'ItemGroup') |
| samelen = len(infilelist) == len(outfilelist) |
| for i, curfile in enumerate(infilelist): |
| if samelen: |
| sole_output = os.path.join(target_private_dir, outfilelist[i]) |
| else: |
| sole_output = '' |
| infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src, target_private_dir)) |
| deps = self.get_target_depend_files(genlist, True) |
| base_args = generator.get_arglist(infilename) |
| outfiles_rel = genlist.get_outputs_for(curfile) |
| outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel] |
| generator_output_files += outfiles |
| args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output) |
| for x in base_args] |
| args = self.replace_outputs(args, target_private_dir, outfiles_rel) |
| args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()) |
| .replace("@BUILD_DIR@", target_private_dir) |
| for x in args] |
| args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args] |
| args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir()) |
| .replace("@BUILD_ROOT@", self.environment.get_build_dir()) |
| for x in args] |
| args = [x.replace('\\', '/') for x in args] |
| # Always use a wrapper because MSBuild eats random characters when |
| # there are many arguments. |
| tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) |
| cmd, _ = self.as_meson_exe_cmdline( |
| exe, |
| self.replace_extra_args(args, genlist), |
| workdir=tdir_abs, |
| capture=outfiles[0] if generator.capture else None, |
| force_serialize=True, |
| env=genlist.env |
| ) |
| deps = cmd[-1:] + deps |
| abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) |
| os.makedirs(abs_pdir, exist_ok=True) |
| cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename) |
| ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd)) |
| ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles) |
| ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps) |
| |
| def generate_custom_generator_commands(self, target, parent_node): |
| generator_output_files = [] |
| custom_target_include_dirs = [] |
| custom_target_output_files = [] |
| for genlist in target.get_generated_sources(): |
| self.generate_genlist_for_target(genlist, target, parent_node, generator_output_files, custom_target_include_dirs, custom_target_output_files) |
| return generator_output_files, custom_target_output_files, custom_target_include_dirs |
| |
| def generate(self, |
| capture: bool = False, |
| vslite_ctx: T.Optional[T.Dict] = None) -> T.Optional[T.Dict]: |
| # Check for (currently) unexpected capture arg use cases - |
| if capture: |
| raise MesonBugException('We do not expect any vs backend to generate with \'capture = True\'') |
| host_machine = self.environment.machines.host.cpu_family |
| if host_machine in {'64', 'x86_64'}: |
| # amd64 or x86_64 |
| target_system = self.environment.machines.host.system |
| if detect_microsoft_gdk(target_system): |
| self.platform = target_system |
| else: |
| self.platform = 'x64' |
| elif host_machine == 'x86': |
| # x86 |
| self.platform = 'Win32' |
| elif host_machine in {'aarch64', 'arm64'}: |
| target_cpu = self.environment.machines.host.cpu |
| if target_cpu == 'arm64ec': |
| self.platform = 'arm64ec' |
| else: |
| self.platform = 'arm64' |
| elif 'arm' in host_machine.lower(): |
| self.platform = 'ARM' |
| else: |
| raise MesonException('Unsupported Visual Studio platform: ' + host_machine) |
| |
| build_machine = self.environment.machines.build.cpu_family |
| if build_machine in {'64', 'x86_64'}: |
| # amd64 or x86_64 |
| self.build_platform = 'x64' |
| elif build_machine == 'x86': |
| # x86 |
| self.build_platform = 'Win32' |
| elif build_machine in {'aarch64', 'arm64'}: |
| target_cpu = self.environment.machines.build.cpu |
| if target_cpu == 'arm64ec': |
| self.build_platform = 'arm64ec' |
| else: |
| self.build_platform = 'arm64' |
| elif 'arm' in build_machine.lower(): |
| self.build_platform = 'ARM' |
| else: |
| raise MesonException('Unsupported Visual Studio platform: ' + build_machine) |
| |
| self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype')) |
| self.optimization = self.environment.coredata.get_option(OptionKey('optimization')) |
| self.debug = self.environment.coredata.get_option(OptionKey('debug')) |
| try: |
| self.sanitize = self.environment.coredata.get_option(OptionKey('b_sanitize')) |
| except MesonException: |
| self.sanitize = 'none' |
| sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln') |
| projlist = self.generate_projects(vslite_ctx) |
| self.gen_testproj() |
| self.gen_installproj() |
| self.gen_regenproj() |
| self.generate_solution(sln_filename, projlist) |
| self.generate_regen_info() |
| Vs2010Backend.touch_regen_timestamp(self.environment.get_build_dir()) |
| |
| @staticmethod |
| def get_regen_stampfile(build_dir: str) -> None: |
| return os.path.join(os.path.join(build_dir, Environment.private_dir), 'regen.stamp') |
| |
| @staticmethod |
| def touch_regen_timestamp(build_dir: str) -> None: |
| with open(Vs2010Backend.get_regen_stampfile(build_dir), 'w', encoding='utf-8'): |
| pass |
| |
| def get_vcvars_command(self): |
| has_arch_values = 'VSCMD_ARG_TGT_ARCH' in os.environ and 'VSCMD_ARG_HOST_ARCH' in os.environ |
| |
| # Use vcvarsall.bat if we found it. |
| if 'VCINSTALLDIR' in os.environ: |
| vs_version = os.environ['VisualStudioVersion'] \ |
| if 'VisualStudioVersion' in os.environ else None |
| relative_path = 'Auxiliary\\Build\\' if vs_version is not None and vs_version >= '15.0' else '' |
| script_path = os.environ['VCINSTALLDIR'] + relative_path + 'vcvarsall.bat' |
| if os.path.exists(script_path): |
| if has_arch_values: |
| target_arch = os.environ['VSCMD_ARG_TGT_ARCH'] |
| host_arch = os.environ['VSCMD_ARG_HOST_ARCH'] |
| else: |
| target_arch = os.environ.get('Platform', 'x86') |
| host_arch = target_arch |
| arch = host_arch + '_' + target_arch if host_arch != target_arch else target_arch |
| return f'"{script_path}" {arch}' |
| |
| # Otherwise try the VS2017 Developer Command Prompt. |
| if 'VS150COMNTOOLS' in os.environ and has_arch_values: |
| script_path = os.environ['VS150COMNTOOLS'] + 'VsDevCmd.bat' |
| if os.path.exists(script_path): |
| return '"%s" -arch=%s -host_arch=%s' % \ |
| (script_path, os.environ['VSCMD_ARG_TGT_ARCH'], os.environ['VSCMD_ARG_HOST_ARCH']) |
| return '' |
| |
| def get_obj_target_deps(self, obj_list): |
| result = {} |
| for o in obj_list: |
| if isinstance(o, build.ExtractedObjects): |
| result[o.target.get_id()] = o.target |
| return result.items() |
| |
| def get_target_deps(self, t: T.Dict[T.Any, T.Union[build.Target, build.CustomTargetIndex]], recursive=False): |
| all_deps: T.Dict[str, build.Target] = {} |
| for target in t.values(): |
| if isinstance(target, build.CustomTargetIndex): |
| # just transfer it to the CustomTarget code |
| target = target.target |
| if isinstance(target, build.CustomTarget): |
| for d in target.get_target_dependencies(): |
| # FIXME: this isn't strictly correct, as the target doesn't |
| # Get dependencies on non-targets, such as Files |
| if isinstance(d, build.Target): |
| all_deps[d.get_id()] = d |
| elif isinstance(target, build.RunTarget): |
| for d in target.get_dependencies(): |
| all_deps[d.get_id()] = d |
| elif isinstance(target, build.BuildTarget): |
| for ldep in target.link_targets: |
| if isinstance(ldep, build.CustomTargetIndex): |
| all_deps[ldep.get_id()] = ldep.target |
| else: |
| all_deps[ldep.get_id()] = ldep |
| for ldep in target.link_whole_targets: |
| if isinstance(ldep, build.CustomTargetIndex): |
| all_deps[ldep.get_id()] = ldep.target |
| else: |
| all_deps[ldep.get_id()] = ldep |
| |
| for ldep in target.link_depends: |
| if isinstance(ldep, build.CustomTargetIndex): |
| all_deps[ldep.get_id()] = ldep.target |
| elif isinstance(ldep, File): |
| # Already built, no target references needed |
| pass |
| else: |
| all_deps[ldep.get_id()] = ldep |
| |
| for obj_id, objdep in self.get_obj_target_deps(target.objects): |
| all_deps[obj_id] = objdep |
| else: |
| raise MesonException(f'Unknown target type for target {target}') |
| |
| for gendep in target.get_generated_sources(): |
| if isinstance(gendep, build.CustomTarget): |
| all_deps[gendep.get_id()] = gendep |
| elif isinstance(gendep, build.CustomTargetIndex): |
| all_deps[gendep.target.get_id()] = gendep.target |
| else: |
| generator = gendep.get_generator() |
| gen_exe = generator.get_exe() |
| if isinstance(gen_exe, build.Executable): |
| all_deps[gen_exe.get_id()] = gen_exe |
| for d in itertools.chain(generator.depends, gendep.depends): |
| if isinstance(d, build.CustomTargetIndex): |
| all_deps[d.get_id()] = d.target |
| elif isinstance(d, build.Target): |
| all_deps[d.get_id()] = d |
| # FIXME: we don't handle other kinds of deps correctly here, such |
| # as GeneratedLists, StructuredSources, and generated File. |
| |
| if not t or not recursive: |
| return all_deps |
| ret = self.get_target_deps(all_deps, recursive) |
| ret.update(all_deps) |
| return ret |
| |
| def generate_solution_dirs(self, ofile: str, parents: T.Sequence[Path]) -> None: |
| prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n' |
| iterpaths = reversed(parents) |
| # Skip first path |
| next(iterpaths) |
| for path in iterpaths: |
| if path not in self.subdirs: |
| basename = path.name |
| identifier = generate_guid_from_path(path, 'subdir') |
| # top-level directories have None as their parent_dir |
| parent_dir = path.parent |
| parent_identifier = self.subdirs[parent_dir][0] \ |
| if parent_dir != PurePath('.') else None |
| self.subdirs[path] = (identifier, parent_identifier) |
| prj_line = prj_templ % ( |
| self.environment.coredata.lang_guids['directory'], |
| basename, basename, self.subdirs[path][0]) |
| ofile.write(prj_line) |
| ofile.write('EndProject\n') |
| |
| def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> None: |
| default_projlist = self.get_build_by_default_targets() |
| default_projlist.update(self.get_testlike_targets()) |
| sln_filename_tmp = sln_filename + '~' |
| # Note using the utf-8 BOM requires the blank line, otherwise Visual Studio Version Selector fails. |
| # Without the BOM, VSVS fails if there is a blank line. |
| with open(sln_filename_tmp, 'w', encoding='utf-8-sig') as ofile: |
| ofile.write('\nMicrosoft Visual Studio Solution File, Format Version %s\n' % self.sln_file_version) |
| ofile.write('# Visual Studio %s\n' % self.sln_version_comment) |
| prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n' |
| for prj in projlist: |
| if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': |
| self.generate_solution_dirs(ofile, prj[1].parents) |
| target = self.build.targets[prj[0]] |
| lang = 'default' |
| if hasattr(target, 'compilers') and target.compilers: |
| for lang_out in target.compilers.keys(): |
| lang = lang_out |
| break |
| prj_line = prj_templ % ( |
| self.environment.coredata.lang_guids[lang], |
| prj[0], prj[1], prj[2]) |
| ofile.write(prj_line) |
| target_dict = {target.get_id(): target} |
| # Get recursive deps |
| recursive_deps = self.get_target_deps( |
| target_dict, recursive=True) |
| ofile.write('EndProject\n') |
| for dep, target in recursive_deps.items(): |
| if prj[0] in default_projlist: |
| default_projlist[dep] = target |
| |
| test_line = prj_templ % (self.environment.coredata.lang_guids['default'], |
| 'RUN_TESTS', 'RUN_TESTS.vcxproj', |
| self.environment.coredata.test_guid) |
| ofile.write(test_line) |
| ofile.write('EndProject\n') |
| if self.gen_lite: # REGEN is replaced by the lighter-weight RECONFIGURE utility, for now. See comment in 'gen_regenproj' |
| regen_proj_name = 'RECONFIGURE' |
| regen_proj_fname = 'RECONFIGURE.vcxproj' |
| else: |
| regen_proj_name = 'REGEN' |
| regen_proj_fname = 'REGEN.vcxproj' |
| regen_line = prj_templ % (self.environment.coredata.lang_guids['default'], |
| regen_proj_name, regen_proj_fname, |
| self.environment.coredata.regen_guid) |
| ofile.write(regen_line) |
| ofile.write('EndProject\n') |
| install_line = prj_templ % (self.environment.coredata.lang_guids['default'], |
| 'RUN_INSTALL', 'RUN_INSTALL.vcxproj', |
| self.environment.coredata.install_guid) |
| ofile.write(install_line) |
| ofile.write('EndProject\n') |
| ofile.write('Global\n') |
| ofile.write('\tGlobalSection(SolutionConfigurationPlatforms) = ' |
| 'preSolution\n') |
| multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype] |
| for buildtype in multi_config_buildtype_list: |
| ofile.write('\t\t%s|%s = %s|%s\n' % |
| (buildtype, self.platform, buildtype, |
| self.platform)) |
| ofile.write('\tEndGlobalSection\n') |
| ofile.write('\tGlobalSection(ProjectConfigurationPlatforms) = ' |
| 'postSolution\n') |
| # REGEN project (multi-)configurations |
| for buildtype in multi_config_buildtype_list: |
| ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % |
| (self.environment.coredata.regen_guid, buildtype, |
| self.platform, buildtype, self.platform)) |
| if not self.gen_lite: # With a 'genvslite'-generated solution, the regen (i.e. reconfigure) utility is only intended to run when the user explicitly builds this proj. |
| ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' % |
| (self.environment.coredata.regen_guid, buildtype, |
| self.platform, buildtype, self.platform)) |
| # Create the solution configuration |
| for project_index, p in enumerate(projlist): |
| if p[3] is MachineChoice.BUILD: |
| config_platform = self.build_platform |
| else: |
| config_platform = self.platform |
| # Add to the list of projects in this solution |
| for buildtype in multi_config_buildtype_list: |
| ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % |
| (p[2], buildtype, self.platform, |
| buildtype, config_platform)) |
| # If we're building the solution with Visual Studio's build system, enable building of buildable |
| # projects. However, if we're building with meson (via --genvslite), then, since each project's |
| # 'build' action just ends up doing the same 'meson compile ...' we don't want the 'solution build' |
| # repeatedly going off and doing the same 'meson compile ...' multiple times over, so we default |
| # to building the startup project, which is the first listed project in the solution file by |
| # default for Visual Studio. The user is free to change this afterwards, but this provides a |
| # sensible default. |
| if (not self.gen_lite or project_index == 0) and \ |
| p[0] in default_projlist and \ |
| not isinstance(self.build.targets[p[0]], build.RunTarget): |
| ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' % |
| (p[2], buildtype, self.platform, |
| buildtype, config_platform)) |
| # RUN_TESTS and RUN_INSTALL project (multi-)configurations |
| for buildtype in multi_config_buildtype_list: |
| ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % |
| (self.environment.coredata.test_guid, buildtype, |
| self.platform, buildtype, self.platform)) |
| ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % |
| (self.environment.coredata.install_guid, buildtype, |
| self.platform, buildtype, self.platform)) |
| ofile.write('\tEndGlobalSection\n') |
| ofile.write('\tGlobalSection(SolutionProperties) = preSolution\n') |
| ofile.write('\t\tHideSolutionNode = FALSE\n') |
| ofile.write('\tEndGlobalSection\n') |
| if self.subdirs: |
| ofile.write('\tGlobalSection(NestedProjects) = ' |
| 'preSolution\n') |
| for p in projlist: |
| if p[1].parent != PurePath('.'): |
| ofile.write("\t\t{{{}}} = {{{}}}\n".format(p[2], self.subdirs[p[1].parent][0])) |
| for subdir in self.subdirs.values(): |
| if subdir[1]: |
| ofile.write("\t\t{{{}}} = {{{}}}\n".format(subdir[0], subdir[1])) |
| ofile.write('\tEndGlobalSection\n') |
| ofile.write('EndGlobal\n') |
| replace_if_different(sln_filename, sln_filename_tmp) |
| |
| def generate_projects(self, vslite_ctx: dict = None) -> T.List[Project]: |
| startup_project = self.environment.coredata.optstore.get_value('backend_startup_project') |
| projlist: T.List[Project] = [] |
| startup_idx = 0 |
| for (i, (name, target)) in enumerate(self.build.targets.items()): |
| if startup_project and startup_project == target.get_basename(): |
| startup_idx = i |
| outdir = Path( |
| self.environment.get_build_dir(), |
| self.get_target_dir(target) |
| ) |
| outdir.mkdir(exist_ok=True, parents=True) |
| fname = name + '.vcxproj' |
| target_dir = PurePath(self.get_target_dir(target)) |
| relname = target_dir / fname |
| projfile_path = outdir / fname |
| proj_uuid = self.environment.coredata.target_guids[name] |
| generated = self.gen_vcxproj(target, str(projfile_path), proj_uuid, vslite_ctx) |
| if generated: |
| projlist.append((name, relname, proj_uuid, target.for_machine)) |
| |
| # Put the startup project first in the project list |
| if startup_idx: |
| projlist.insert(0, projlist.pop(startup_idx)) |
| |
| return projlist |
| |
| def split_sources(self, srclist): |
| sources = [] |
| headers = [] |
| objects = [] |
| languages = [] |
| for i in srclist: |
| if self.environment.is_header(i): |
| headers.append(i) |
| elif self.environment.is_object(i): |
| objects.append(i) |
| elif self.environment.is_source(i): |
| sources.append(i) |
| lang = self.lang_from_source_file(i) |
| if lang not in languages: |
| languages.append(lang) |
| elif self.environment.is_library(i): |
| pass |
| else: |
| # Everything that is not an object or source file is considered a header. |
| headers.append(i) |
| return sources, headers, objects, languages |
| |
| def target_to_build_root(self, target): |
| if self.get_target_dir(target) == '': |
| return '' |
| |
| directories = os.path.normpath(self.get_target_dir(target)).split(os.sep) |
| return os.sep.join(['..'] * len(directories)) |
| |
| def quote_arguments(self, arr): |
| return ['"%s"' % i for i in arr] |
| |
| def add_project_reference(self, root: ET.Element, include: str, projid: str, link_outputs: bool = False) -> None: |
| ig = ET.SubElement(root, 'ItemGroup') |
| pref = ET.SubElement(ig, 'ProjectReference', Include=include) |
| ET.SubElement(pref, 'Project').text = '{%s}' % projid |
| if not link_outputs: |
| # Do not link in generated .lib files from dependencies automatically. |
| # We only use the dependencies for ordering and link in the generated |
| # objects and .lib files manually. |
| ET.SubElement(pref, 'LinkLibraryDependencies').text = 'false' |
| |
| def add_target_deps(self, root: ET.Element, target): |
| target_dict = {target.get_id(): target} |
| for dep in self.get_target_deps(target_dict).values(): |
| if dep.get_id() in self.handled_target_deps[target.get_id()]: |
| # This dependency was already handled manually. |
| continue |
| relpath = self.get_target_dir_relative_to(dep, target) |
| vcxproj = os.path.join(relpath, dep.get_id() + '.vcxproj') |
| tid = self.environment.coredata.target_guids[dep.get_id()] |
| self.add_project_reference(root, vcxproj, tid) |
| |
| def create_basic_project(self, target_name, *, |
| temp_dir, |
| guid, |
| conftype='Utility', |
| target_ext=None, |
| target_platform=None, |
| gen_manifest=True) -> T.Tuple[ET.Element, ET.Element]: |
| root = ET.Element('Project', {'DefaultTargets': "Build", |
| 'ToolsVersion': '4.0', |
| 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}) |
| |
| confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'}) |
| if not target_platform: |
| target_platform = self.platform |
| |
| multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype] |
| for buildtype in multi_config_buildtype_list: |
| prjconf = ET.SubElement(confitems, 'ProjectConfiguration', |
| {'Include': buildtype + '|' + target_platform}) |
| ET.SubElement(prjconf, 'Configuration').text = buildtype |
| ET.SubElement(prjconf, 'Platform').text = target_platform |
| |
| # Globals |
| globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals') |
| guidelem = ET.SubElement(globalgroup, 'ProjectGuid') |
| guidelem.text = '{%s}' % guid |
| kw = ET.SubElement(globalgroup, 'Keyword') |
| kw.text = self.platform + 'Proj' |
| |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props') |
| |
| # Configuration |
| type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') |
| ET.SubElement(type_config, 'ConfigurationType').text = conftype |
| if self.platform_toolset: |
| ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset |
| |
| # This must come AFTER the '<PropertyGroup Label="Configuration">' element; importing before the 'PlatformToolset' elt |
| # gets set leads to msbuild failures reporting - |
| # "The build tools for v142 (Platform Toolset = 'v142') cannot be found. ... please install v142 build tools." |
| # This is extremely unhelpful and misleading since the v14x build tools ARE installed. |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props') |
| |
| # This attribute makes sure project names are displayed as expected in solution files even when their project file names differ |
| pname = ET.SubElement(globalgroup, 'ProjectName') |
| pname.text = target_name |
| |
| if not self.gen_lite: # Plenty of elements aren't necessary for 'makefile'-style project that just redirects to meson builds |
| # XXX Wasn't here before for anything but gen_vcxproj , but seems fine? |
| ns = ET.SubElement(globalgroup, 'RootNamespace') |
| ns.text = target_name |
| |
| p = ET.SubElement(globalgroup, 'Platform') |
| p.text = target_platform |
| if self.windows_target_platform_version: |
| ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version |
| ET.SubElement(globalgroup, 'UseMultiToolTask').text = 'true' |
| |
| ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte' |
| # Fixme: wasn't here before for gen_vcxproj() |
| ET.SubElement(type_config, 'UseOfMfc').text = 'false' |
| |
| # Project information |
| direlem = ET.SubElement(root, 'PropertyGroup') |
| fver = ET.SubElement(direlem, '_ProjectFileVersion') |
| fver.text = self.project_file_version |
| outdir = ET.SubElement(direlem, 'OutDir') |
| outdir.text = '.\\' |
| intdir = ET.SubElement(direlem, 'IntDir') |
| intdir.text = temp_dir + '\\' |
| |
| tname = ET.SubElement(direlem, 'TargetName') |
| tname.text = target_name |
| |
| if target_ext: |
| ET.SubElement(direlem, 'TargetExt').text = target_ext |
| |
| ET.SubElement(direlem, 'EmbedManifest').text = 'false' |
| if not gen_manifest: |
| ET.SubElement(direlem, 'GenerateManifest').text = 'false' |
| |
| return (root, type_config) |
| |
| def gen_run_target_vcxproj(self, target: build.RunTarget, ofname: str, guid: str) -> None: |
| (root, type_config) = self.create_basic_project(target.name, |
| temp_dir=target.get_id(), |
| guid=guid, |
| gen_manifest=self.get_gen_manifest(target)) |
| depend_files = self.get_target_depend_files(target) |
| |
| if not target.command: |
| # This is an alias target and thus doesn't run any command. It's |
| # enough to emit the references to the other projects for them to |
| # be built/run/..., if necessary. |
| assert isinstance(target, build.AliasTarget) |
| assert len(depend_files) == 0 |
| else: |
| assert not isinstance(target, build.AliasTarget) |
| |
| target_env = self.get_run_target_env(target) |
| _, _, cmd_raw = self.eval_custom_target_command(target) |
| wrapper_cmd, _ = self.as_meson_exe_cmdline(target.command[0], cmd_raw[1:], |
| force_serialize=True, env=target_env, |
| verbose=True) |
| self.add_custom_build(root, 'run_target', ' '.join(self.quote_arguments(wrapper_cmd)), |
| deps=depend_files) |
| |
| # The import is needed even for alias targets, otherwise the build |
| # target isn't defined |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| self.add_regen_dependency(root) |
| self.add_target_deps(root, target) |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| |
| def gen_custom_target_vcxproj(self, target: build.CustomTarget, ofname: str, guid: str) -> None: |
| if target.for_machine is MachineChoice.BUILD: |
| platform = self.build_platform |
| else: |
| platform = self.platform |
| (root, type_config) = self.create_basic_project(target.name, |
| temp_dir=target.get_id(), |
| guid=guid, |
| target_platform=platform, |
| gen_manifest=self.get_gen_manifest(target)) |
| # We need to always use absolute paths because our invocation is always |
| # from the target dir, not the build root. |
| target.absolute_paths = True |
| (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True) |
| depend_files = self.get_target_depend_files(target, True) |
| # Always use a wrapper because MSBuild eats random characters when |
| # there are many arguments. |
| tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) |
| extra_bdeps = target.get_transitive_build_target_deps() |
| wrapper_cmd, _ = self.as_meson_exe_cmdline(target.command[0], cmd[1:], |
| # All targets run from the target dir |
| workdir=tdir_abs, |
| extra_bdeps=extra_bdeps, |
| capture=ofilenames[0] if target.capture else None, |
| feed=srcs[0] if target.feed else None, |
| force_serialize=True, |
| env=target.env, |
| verbose=target.console) |
| if target.build_always_stale: |
| # Use a nonexistent file to always consider the target out-of-date. |
| ofilenames += [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(), |
| 'outofdate.file'))] |
| self.add_custom_build(root, 'custom_target', ' '.join(self.quote_arguments(wrapper_cmd)), |
| deps=wrapper_cmd[-1:] + srcs + depend_files, outputs=ofilenames, |
| verify_files=not target.build_always_stale) |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| self.generate_custom_generator_commands(target, root) |
| self.add_regen_dependency(root) |
| self.add_target_deps(root, target) |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| |
| def gen_compile_target_vcxproj(self, target: build.CompileTarget, ofname: str, guid: str) -> None: |
| if target.for_machine is MachineChoice.BUILD: |
| platform = self.build_platform |
| else: |
| platform = self.platform |
| (root, type_config) = self.create_basic_project(target.name, |
| temp_dir=target.get_id(), |
| guid=guid, |
| target_platform=platform, |
| gen_manifest=self.get_gen_manifest(target)) |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| target.generated = [self.compile_target_to_generator(target)] |
| target.sources = [] |
| self.generate_custom_generator_commands(target, root) |
| self.add_regen_dependency(root) |
| self.add_target_deps(root, target) |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| |
| @classmethod |
| def lang_from_source_file(cls, src): |
| ext = src.split('.')[-1] |
| if ext in compilers.c_suffixes: |
| return 'c' |
| if ext in compilers.cpp_suffixes: |
| return 'cpp' |
| raise MesonException(f'Could not guess language from source file {src}.') |
| |
| def add_pch(self, pch_sources, lang, inc_cl): |
| if lang in pch_sources: |
| self.use_pch(pch_sources, lang, inc_cl) |
| |
| def create_pch(self, pch_sources, lang, inc_cl): |
| pch = ET.SubElement(inc_cl, 'PrecompiledHeader') |
| pch.text = 'Create' |
| self.add_pch_files(pch_sources, lang, inc_cl) |
| |
| def use_pch(self, pch_sources, lang, inc_cl): |
| pch = ET.SubElement(inc_cl, 'PrecompiledHeader') |
| pch.text = 'Use' |
| header = self.add_pch_files(pch_sources, lang, inc_cl) |
| pch_include = ET.SubElement(inc_cl, 'ForcedIncludeFiles') |
| pch_include.text = header + ';%(ForcedIncludeFiles)' |
| |
| def add_pch_files(self, pch_sources, lang, inc_cl): |
| header = os.path.basename(pch_sources[lang][0]) |
| pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile') |
| # When USING PCHs, MSVC will not do the regular include |
| # directory lookup, but simply use a string match to find the |
| # PCH to use. That means the #include directive must match the |
| # pch_file.text used during PCH CREATION verbatim. |
| # When CREATING a PCH, MSVC will do the include directory |
| # lookup to find the actual PCH header to use. Thus, the PCH |
| # header must either be in the include_directories of the target |
| # or be in the same directory as the PCH implementation. |
| pch_file.text = header |
| pch_out = ET.SubElement(inc_cl, 'PrecompiledHeaderOutputFile') |
| pch_out.text = f'$(IntDir)$(TargetName)-{lang}.pch' |
| |
| # Need to set the name for the pdb, as cl otherwise gives it a static |
| # name. Which leads to problems when there is more than one pch |
| # (e.g. for different languages). |
| pch_pdb = ET.SubElement(inc_cl, 'ProgramDataBaseFileName') |
| pch_pdb.text = f'$(IntDir)$(TargetName)-{lang}.pdb' |
| |
| return header |
| |
| def is_argument_with_msbuild_xml_entry(self, entry): |
| # Remove arguments that have a top level XML entry so |
| # they are not used twice. |
| # FIXME add args as needed. |
| if entry[1:].startswith('fsanitize'): |
| return True |
| return entry[1:].startswith('M') |
| |
| def add_additional_options(self, lang, parent_node, file_args): |
| args = [] |
| for arg in file_args[lang].to_native(): |
| if self.is_argument_with_msbuild_xml_entry(arg): |
| continue |
| if arg == '%(AdditionalOptions)': |
| args.append(arg) |
| else: |
| args.append(self.escape_additional_option(arg)) |
| ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args) |
| |
| # Set up each project's source file ('CLCompile') element with appropriate preprocessor, include dir, and compile option values for correct intellisense. |
| def add_project_nmake_defs_incs_and_opts(self, parent_node, src: str, defs_paths_opts_per_lang_and_buildtype: dict, platform: str): |
| # For compactness, sources whose type matches the primary src type (i.e. most frequent in the set of source types used in the target/project, |
| # according to the 'captured_build_args' map), can simply reference the preprocessor definitions, include dirs, and compile option NMake fields of |
| # the project itself. |
| # However, if a src is of a non-primary type, it could have totally different defs/dirs/options so we're going to have to fill in the full, verbose |
| # set of values for these fields, which needs to be fully expanded per build type / configuration. |
| # |
| # FIXME: Suppose a project contains .cpp and .c src files with different compile defs/dirs/options, while also having .h files, some of which |
| # are included by .cpp sources and others included by .c sources: How do we know whether the .h source should be using the .cpp or .c src |
| # defs/dirs/options? Might it also be possible for a .h header to be shared between .cpp and .c sources? If so, I don't see how we can |
| # correctly configure these intellisense fields. |
| # For now, all sources/headers that fail to find their extension's language in the '...nmake_defs_paths_opts...' map will just adopt the project |
| # defs/dirs/opts that are set for the nominal 'primary' src type. |
| ext = src.split('.')[-1] |
| lang = compilers.compilers.SUFFIX_TO_LANG.get(ext, None) |
| if lang in defs_paths_opts_per_lang_and_buildtype.keys(): |
| # This is a non-primary src type for which can't simply reference the project's nmake fields; |
| # we must laboriously fill in the fields for all buildtypes. |
| for buildtype in coredata.get_genvs_default_buildtype_list(): |
| (defs, paths, opts) = defs_paths_opts_per_lang_and_buildtype[lang][buildtype] |
| condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{platform}\'' |
| ET.SubElement(parent_node, 'PreprocessorDefinitions', Condition=condition).text = defs |
| ET.SubElement(parent_node, 'AdditionalIncludeDirectories', Condition=condition).text = paths |
| ET.SubElement(parent_node, 'AdditionalOptions', Condition=condition).text = opts |
| else: # Can't find bespoke nmake defs/dirs/opts fields for this extention, so just reference the project's fields |
| ET.SubElement(parent_node, 'PreprocessorDefinitions').text = '$(NMakePreprocessorDefinitions)' |
| ET.SubElement(parent_node, 'AdditionalIncludeDirectories').text = '$(NMakeIncludeSearchPath)' |
| ET.SubElement(parent_node, 'AdditionalOptions').text = '$(AdditionalOptions)' |
| |
| def add_preprocessor_defines(self, lang, parent_node, file_defines): |
| defines = [] |
| for define in file_defines[lang]: |
| if define == '%(PreprocessorDefinitions)': |
| defines.append(define) |
| else: |
| defines.append(self.escape_preprocessor_define(define)) |
| ET.SubElement(parent_node, "PreprocessorDefinitions").text = ';'.join(defines) |
| |
| def add_include_dirs(self, lang, parent_node, file_inc_dirs): |
| dirs = file_inc_dirs[lang] |
| ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs) |
| |
| @staticmethod |
| def escape_preprocessor_define(define: str) -> str: |
| # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx |
| table = str.maketrans({'%': '%25', '$': '%24', '@': '%40', |
| "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', |
| # We need to escape backslash because it'll be un-escaped by |
| # Windows during process creation when it parses the arguments |
| # Basically, this converts `\` to `\\`. |
| '\\': '\\\\'}) |
| return define.translate(table) |
| |
| @staticmethod |
| def escape_additional_option(option: str) -> str: |
| # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx |
| table = str.maketrans({'%': '%25', '$': '%24', '@': '%40', |
| "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', ' ': '%20'}) |
| option = option.translate(table) |
| # Since we're surrounding the option with ", if it ends in \ that will |
| # escape the " when the process arguments are parsed and the starting |
| # " will not terminate. So we escape it if that's the case. I'm not |
| # kidding, this is how escaping works for process args on Windows. |
| if option.endswith('\\'): |
| option += '\\' |
| return f'"{option}"' |
| |
| @staticmethod |
| def split_link_args(args): |
| """ |
| Split a list of link arguments into three lists: |
| * library search paths |
| * library filenames (or paths) |
| * other link arguments |
| """ |
| lpaths = [] |
| libs = [] |
| other = [] |
| for arg in args: |
| if arg.startswith('/LIBPATH:'): |
| lpath = arg[9:] |
| # De-dup library search paths by removing older entries when |
| # a new one is found. This is necessary because unlike other |
| # search paths such as the include path, the library is |
| # searched for in the newest (right-most) search path first. |
| if lpath in lpaths: |
| lpaths.remove(lpath) |
| lpaths.append(lpath) |
| elif arg.startswith(('/', '-')): |
| other.append(arg) |
| # It's ok if we miss libraries with non-standard extensions here. |
| # They will go into the general link arguments. |
| elif arg.endswith('.lib') or arg.endswith('.a'): |
| # De-dup |
| if arg not in libs: |
| libs.append(arg) |
| else: |
| other.append(arg) |
| return lpaths, libs, other |
| |
| def _get_cl_compiler(self, target): |
| for lang, c in target.compilers.items(): |
| if lang in {'c', 'cpp'}: |
| return c |
| # No source files, only objects, but we still need a compiler, so |
| # return a found compiler |
| if len(target.objects) > 0: |
| for lang, c in self.environment.coredata.compilers[target.for_machine].items(): |
| if lang in {'c', 'cpp'}: |
| return c |
| raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.') |
| |
| def _prettyprint_vcxproj_xml(self, tree: ET.ElementTree, ofname: str) -> None: |
| ofname_tmp = ofname + '~' |
| tree.write(ofname_tmp, encoding='utf-8', xml_declaration=True) |
| |
| # ElementTree cannot do pretty-printing, so do it manually |
| doc = xml.dom.minidom.parse(ofname_tmp) |
| with open(ofname_tmp, 'w', encoding='utf-8') as of: |
| of.write(doc.toprettyxml()) |
| replace_if_different(ofname, ofname_tmp) |
| |
| # Returns: (target_args,file_args), (target_defines,file_defines), (target_inc_dirs,file_inc_dirs) |
| def get_args_defines_and_inc_dirs(self, target, compiler, generated_files_include_dirs, proj_to_src_root, proj_to_src_dir, build_args): |
| # Arguments, include dirs, defines for all files in the current target |
| target_args = [] |
| target_defines = [] |
| target_inc_dirs = [] |
| # Arguments, include dirs, defines passed to individual files in |
| # a target; perhaps because the args are language-specific |
| # |
| # file_args is also later split out into defines and include_dirs in |
| # case someone passed those in there |
| file_args: T.Dict[str, CompilerArgs] = {l: c.compiler_args() for l, c in target.compilers.items()} |
| file_defines = {l: [] for l in target.compilers} |
| file_inc_dirs = {l: [] for l in target.compilers} |
| # The order in which these compile args are added must match |
| # generate_single_compile() and generate_basic_compiler_args() |
| for l, comp in target.compilers.items(): |
| if l in file_args: |
| file_args[l] += compilers.get_base_compile_args( |
| target.get_options(), comp, self.environment) |
| file_args[l] += comp.get_option_compile_args( |
| target.get_options()) |
| |
| # Add compile args added using add_project_arguments() |
| for l, args in self.build.projects_args[target.for_machine].get(target.subproject, {}).items(): |
| if l in file_args: |
| file_args[l] += args |
| # Add compile args added using add_global_arguments() |
| # These override per-project arguments |
| for l, args in self.build.global_args[target.for_machine].items(): |
| if l in file_args: |
| file_args[l] += args |
| # Compile args added from the env or cross file: CFLAGS/CXXFLAGS, etc. We want these |
| # to override all the defaults, but not the per-target compile args. |
| for l in file_args.keys(): |
| file_args[l] += target.get_option(OptionKey('args', machine=target.for_machine, lang=l)) |
| for args in file_args.values(): |
| # This is where Visual Studio will insert target_args, target_defines, |
| # etc, which are added later from external deps (see below). |
| args += ['%(AdditionalOptions)', '%(PreprocessorDefinitions)', '%(AdditionalIncludeDirectories)'] |
| # Add custom target dirs as includes automatically, but before |
| # target-specific include dirs. See _generate_single_compile() in |
| # the ninja backend for caveats. |
| args += ['-I' + arg for arg in generated_files_include_dirs] |
| # Add include dirs from the `include_directories:` kwarg on the target |
| # and from `include_directories:` of internal deps of the target. |
| # |
| # Target include dirs should override internal deps include dirs. |
| # This is handled in BuildTarget.process_kwargs() |
| # |
| # Include dirs from internal deps should override include dirs from |
| # external deps and must maintain the order in which they are |
| # specified. Hence, we must reverse so that the order is preserved. |
| # |
| # These are per-target, but we still add them as per-file because we |
| # need them to be looked in first. |
| for d in reversed(target.get_include_dirs()): |
| # reversed is used to keep order of includes |
| for i in reversed(d.get_incdirs()): |
| curdir = os.path.join(d.get_curdir(), i) |
| try: |
| # Add source subdir first so that the build subdir overrides it |
| args.append('-I' + os.path.join(proj_to_src_root, curdir)) # src dir |
| args.append('-I' + self.relpath(curdir, target.subdir)) # build dir |
| except ValueError: |
| # Include is on different drive |
| args.append('-I' + os.path.normpath(curdir)) |
| for i in d.get_extra_build_dirs(): |
| curdir = os.path.join(d.get_curdir(), i) |
| args.append('-I' + self.relpath(curdir, target.subdir)) # build dir |
| # Add per-target compile args, f.ex, `c_args : ['/DFOO']`. We set these |
| # near the end since these are supposed to override everything else. |
| for l, args in target.extra_args.items(): |
| if l in file_args: |
| file_args[l] += args |
| # The highest priority includes. In order of directory search: |
| # target private dir, target build dir, target source dir |
| for args in file_args.values(): |
| t_inc_dirs = [self.relpath(self.get_target_private_dir(target), |
| self.get_target_dir(target))] |
| if target.implicit_include_directories: |
| t_inc_dirs += ['.', proj_to_src_dir] |
| args += ['-I' + arg for arg in t_inc_dirs] |
| |
| # Split preprocessor defines and include directories out of the list of |
| # all extra arguments. The rest go into %(AdditionalOptions). |
| for l, args in file_args.items(): |
| for arg in args[:]: |
| if arg.startswith(('-D', '/D')) or arg == '%(PreprocessorDefinitions)': |
| file_args[l].remove(arg) |
| # Don't escape the marker |
| if arg == '%(PreprocessorDefinitions)': |
| define = arg |
| else: |
| define = arg[2:] |
| # De-dup |
| if define not in file_defines[l]: |
| file_defines[l].append(define) |
| elif arg.startswith(('-I', '/I')) or arg == '%(AdditionalIncludeDirectories)': |
| file_args[l].remove(arg) |
| # Don't escape the marker |
| if arg == '%(AdditionalIncludeDirectories)': |
| inc_dir = arg |
| else: |
| inc_dir = arg[2:] |
| # De-dup |
| if inc_dir not in file_inc_dirs[l]: |
| file_inc_dirs[l].append(inc_dir) |
| # Add include dirs to target as well so that "Go to Document" works in headers |
| if inc_dir not in target_inc_dirs: |
| target_inc_dirs.append(inc_dir) |
| |
| # Split compile args needed to find external dependencies |
| # Link args are added while generating the link command |
| for d in reversed(target.get_external_deps()): |
| # Cflags required by external deps might have UNIX-specific flags, |
| # so filter them out if needed |
| if d.name != 'openmp': |
| d_compile_args = compiler.unix_args_to_native(d.get_compile_args()) |
| for arg in d_compile_args: |
| if arg.startswith(('-D', '/D')): |
| define = arg[2:] |
| # De-dup |
| if define in target_defines: |
| target_defines.remove(define) |
| target_defines.append(define) |
| elif arg.startswith(('-I', '/I')): |
| inc_dir = arg[2:] |
| # De-dup |
| if inc_dir not in target_inc_dirs: |
| target_inc_dirs.append(inc_dir) |
| else: |
| target_args.append(arg) |
| |
| if '/Gw' in build_args: |
| target_args.append('/Gw') |
| |
| return (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs) |
| |
| @staticmethod |
| def get_build_args(compiler, optimization_level: str, debug: bool, sanitize: str) -> T.List[str]: |
| build_args = compiler.get_optimization_args(optimization_level) |
| build_args += compiler.get_debug_args(debug) |
| build_args += compiler.sanitizer_compile_args(sanitize) |
| |
| return build_args |
| |
| # Used in populating a simple nmake-style project's intellisense fields. |
| # Given a list of compile args, for example - |
| # [ '-I..\\some\\dir\\include', '-I../../some/other/dir', '/MDd', '/W2', '/std:c++17', '/Od', '/Zi', '-DSOME_DEF=1', '-DANOTHER_DEF=someval', ...] |
| # returns a tuple of pre-processor defs (for this example) - |
| # 'SOME_DEF=1;ANOTHER_DEF=someval;' |
| # and include paths, e.g. - |
| # '..\\some\\dir\\include;../../some/other/dir;' |
| # and finally any remaining compiler options, e.g. - |
| # '/MDd /W2 /std:c++17 /Od/Zi' |
| @staticmethod |
| def _extract_nmake_fields(captured_build_args: list[str]) -> T.Tuple[str, str, str]: |
| include_dir_options = [ |
| '-I', |
| '/I', |
| '-isystem', # regular gcc / clang option to denote system header include search paths |
| '/clang:-isystem', # clang-cl (msvc 'cl'-style clang wrapper) option to pass '-isystem' option to clang driver |
| '/imsvc', # clang-cl option to 'Add directory to system include search path' |
| '/external:I', # msvc cl option to add 'external' include search paths |
| ] |
| |
| defs = '' |
| paths = '$(VC_IncludePath);$(WindowsSDK_IncludePath);' |
| additional_opts = '' |
| for arg in captured_build_args: |
| if arg.startswith(('-D', '/D')): |
| defs += arg[2:] + ';' |
| else: |
| opt_match = next((opt for opt in include_dir_options if arg.startswith(opt)), None) |
| if opt_match: |
| paths += arg[len(opt_match):] + ';' |
| elif arg.startswith(('-', '/')): |
| additional_opts += arg + ' ' |
| return (defs, paths, additional_opts) |
| |
| @staticmethod |
| def get_nmake_base_meson_command_and_exe_search_paths() -> T.Tuple[str, str]: |
| meson_cmd_list = mesonlib.get_meson_command() |
| assert (len(meson_cmd_list) == 1) or (len(meson_cmd_list) == 2) |
| # We expect get_meson_command() to either be of the form - |
| # 1: ['path/to/meson.exe'] |
| # or - |
| # 2: ['path/to/python.exe', 'and/path/to/meson.py'] |
| # so we'd like to ensure our makefile-style project invokes the same meson executable or python src as this instance. |
| exe_search_paths = os.path.dirname(meson_cmd_list[0]) |
| nmake_base_meson_command = os.path.basename(meson_cmd_list[0]) |
| if len(meson_cmd_list) != 1: |
| # We expect to be dealing with case '2', shown above. |
| # With Windows, it's also possible that we get a path to the second element of meson_cmd_list that contains spaces |
| # (e.g. 'and/path to/meson.py'). So, because this will end up directly in the makefile/NMake command lines, we'd |
| # better always enclose it in quotes. Only strictly necessary for paths with spaces but no harm for paths without - |
| nmake_base_meson_command += ' \"' + meson_cmd_list[1] + '\"' |
| exe_search_paths += ';' + os.path.dirname(meson_cmd_list[1]) |
| |
| # Additionally, in some cases, we appear to have to add 'C:\Windows\system32;C:\Windows' to the 'Path' environment (via the |
| # ExecutablePath element), without which, the 'meson compile ...' (NMakeBuildCommandLine) command can fail (failure to find |
| # stdio.h and similar), so something is quietly switching some critical build behaviour based on the presence of these in |
| # the 'Path'. |
| # Not sure if this ultimately comes down to some 'find and guess' hidden behaviours within meson or within MSVC tools, but |
| # I guess some projects may implicitly rely on this behaviour. |
| # Things would be cleaner, more robust, repeatable, and portable if meson (and msvc tools) replaced all this kind of |
| # find/guess behaviour with the requirement that things just be explicitly specified by the user. |
| # An example of this can be seen with - |
| # 1: Download https://github.com/facebook/zstd source |
| # 2: cd to the 'zstd-dev\build\meson' dir |
| # 3: meson setup -Dbin_programs=true -Dbin_contrib=true --genvslite vs2022 builddir_vslite |
| # 4: Open the generated 'builddir_vslite_vs\zstd.sln' and build through a project, which should explicitly add the above to |
| # the project's 'Executable Directories' paths and build successfully. |
| # 5: Remove 'C:\Windows\system32;C:\Windows;' from the same project's 'Executable Directories' paths and rebuild. |
| # This should now fail. |
| # It feels uncomfortable to do this but what better alternative is there (and might this introduce new problems)? - |
| exe_search_paths += ';C:\\Windows\\system32;C:\\Windows' |
| # A meson project that explicitly specifies compiler/linker tools and sdk/include paths is not going to have any problems |
| # with this addition. |
| |
| return (nmake_base_meson_command, exe_search_paths) |
| |
| def add_gen_lite_makefile_vcxproj_elements(self, |
| root: ET.Element, |
| platform: str, |
| target_ext: str, |
| vslite_ctx: dict, |
| target, |
| proj_to_build_root: str, |
| primary_src_lang: T.Optional[str]) -> None: |
| ET.SubElement(root, 'ImportGroup', Label='ExtensionSettings') |
| ET.SubElement(root, 'ImportGroup', Label='Shared') |
| prop_sheets_grp = ET.SubElement(root, 'ImportGroup', Label='PropertySheets') |
| ET.SubElement(prop_sheets_grp, 'Import', {'Project': r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props', |
| 'Condition': r"exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')", |
| 'Label': 'LocalAppDataPlatform' |
| }) |
| ET.SubElement(root, 'PropertyGroup', Label='UserMacros') |
| |
| (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths() |
| |
| # Relative path from this .vcxproj to the directory containing the set of '..._[debug/debugoptimized/release]' setup meson build dirs. |
| proj_to_multiconfigured_builds_parent_dir = os.path.join(proj_to_build_root, '..') |
| |
| # Conditional property groups per configuration (buildtype). E.g. - |
| # <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release|x64'"> |
| multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() |
| for buildtype in multi_config_buildtype_list: |
| per_config_prop_group = ET.SubElement(root, 'PropertyGroup', Condition=f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{platform}\'') |
| (_, build_dir_tail) = os.path.split(self.src_to_build) |
| meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example. |
| proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype)) |
| ET.SubElement(per_config_prop_group, 'OutDir').text = f'{proj_to_build_dir_for_buildtype}\\' |
| ET.SubElement(per_config_prop_group, 'IntDir').text = f'{proj_to_build_dir_for_buildtype}\\' |
| ET.SubElement(per_config_prop_group, 'NMakeBuildCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}"' |
| ET.SubElement(per_config_prop_group, 'NMakeOutput').text = f'$(OutDir){target.name}{target_ext}' |
| captured_build_args = vslite_ctx[buildtype][target.get_id()] |
| # 'captured_build_args' is a dictionary, mapping from each src file type to a list of compile args to use for that type. |
| # Usually, there's just one but we could have multiple src types. However, since there's only one field for the makefile |
| # project's NMake... preprocessor/include intellisense fields, we'll just use the first src type we have to fill in |
| # these fields. Then, any src files in this VS project that aren't of this first src type will then need to override |
| # its intellisense fields instead of simply referencing the values in the project. |
| ET.SubElement(per_config_prop_group, 'NMakeReBuildCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}" --clean && {nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}"' |
| ET.SubElement(per_config_prop_group, 'NMakeCleanCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}" --clean' |
| # Need to set the 'ExecutablePath' element for the above NMake... commands to be able to invoke the meson command. |
| ET.SubElement(per_config_prop_group, 'ExecutablePath').text = exe_search_paths |
| # We may not have any src files and so won't have a primary src language. In which case, we've nothing to fill in for this target's intellisense fields - |
| if primary_src_lang: |
| primary_src_type_build_args = captured_build_args[primary_src_lang] |
| preproc_defs, inc_paths, other_compile_opts = Vs2010Backend._extract_nmake_fields(primary_src_type_build_args) |
| ET.SubElement(per_config_prop_group, 'NMakePreprocessorDefinitions').text = preproc_defs |
| ET.SubElement(per_config_prop_group, 'NMakeIncludeSearchPath').text = inc_paths |
| ET.SubElement(per_config_prop_group, 'AdditionalOptions').text = other_compile_opts |
| |
| # Unless we explicitly specify the following empty path elements, the project is assigned a load of nasty defaults that fill these |
| # with values like - |
| # $(VC_IncludePath);$(WindowsSDK_IncludePath); |
| # which are all based on the current install environment (a recipe for non-reproducibility problems), not the paths that will be used by |
| # the actual meson compile jobs. Although these elements look like they're only for MSBuild operations, they're not needed with our simple, |
| # lite/makefile-style projects so let's just remove them in case they do get used/confused by intellisense. |
| ET.SubElement(per_config_prop_group, 'IncludePath') |
| ET.SubElement(per_config_prop_group, 'ExternalIncludePath') |
| ET.SubElement(per_config_prop_group, 'ReferencePath') |
| ET.SubElement(per_config_prop_group, 'LibraryPath') |
| ET.SubElement(per_config_prop_group, 'LibraryWPath') |
| ET.SubElement(per_config_prop_group, 'SourcePath') |
| ET.SubElement(per_config_prop_group, 'ExcludePath') |
| |
| def add_non_makefile_vcxproj_elements( |
| self, |
| root: ET.Element, |
| type_config: ET.Element, |
| target, |
| platform: str, |
| subsystem, |
| build_args, |
| target_args, |
| target_defines, |
| target_inc_dirs, |
| file_args |
| ) -> None: |
| compiler = self._get_cl_compiler(target) |
| buildtype_link_args = compiler.get_optimization_link_args(self.optimization) |
| |
| # Prefix to use to access the build root from the vcxproj dir |
| down = self.target_to_build_root(target) |
| |
| # FIXME: Should the following just be set in create_basic_project(), even if |
| # irrelevant for current target? |
| |
| # FIXME: Meson's LTO support needs to be integrated here |
| ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false' |
| # Let VS auto-set the RTC level |
| ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default' |
| # Incremental linking increases code size |
| if '/INCREMENTAL:NO' in buildtype_link_args: |
| ET.SubElement(type_config, 'LinkIncremental').text = 'false' |
| |
| # Build information |
| compiles = ET.SubElement(root, 'ItemDefinitionGroup') |
| clconf = ET.SubElement(compiles, 'ClCompile') |
| if True in ((dep.name == 'openmp') for dep in target.get_external_deps()): |
| ET.SubElement(clconf, 'OpenMPSupport').text = 'true' |
| # CRT type; debug or release |
| vscrt_type = target.get_option(OptionKey('b_vscrt')) |
| vscrt_val = compiler.get_crt_val(vscrt_type, self.buildtype) |
| if vscrt_val == 'mdd': |
| ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' |
| ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL' |
| elif vscrt_val == 'mt': |
| # FIXME, wrong |
| ET.SubElement(type_config, 'UseDebugLibraries').text = 'false' |
| ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded' |
| elif vscrt_val == 'mtd': |
| # FIXME, wrong |
| ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' |
| ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug' |
| else: |
| ET.SubElement(type_config, 'UseDebugLibraries').text = 'false' |
| ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL' |
| # Sanitizers |
| if '/fsanitize=address' in build_args: |
| ET.SubElement(type_config, 'EnableASAN').text = 'true' |
| # Debug format |
| if '/ZI' in build_args: |
| ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue' |
| elif '/Zi' in build_args: |
| ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase' |
| elif '/Z7' in build_args: |
| ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle' |
| else: |
| ET.SubElement(clconf, 'DebugInformationFormat').text = 'None' |
| # Runtime checks |
| if '/RTC1' in build_args: |
| ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks' |
| elif '/RTCu' in build_args: |
| ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck' |
| elif '/RTCs' in build_args: |
| ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck' |
| # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise |
| # cl will give warning D9025: overriding '/Ehs' with cpp_eh value |
| if 'cpp' in target.compilers: |
| eh = target.get_option(OptionKey('eh', machine=target.for_machine, lang='cpp')) |
| if eh == 'a': |
| ET.SubElement(clconf, 'ExceptionHandling').text = 'Async' |
| elif eh == 's': |
| ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow' |
| elif eh == 'none': |
| ET.SubElement(clconf, 'ExceptionHandling').text = 'false' |
| else: # 'sc' or 'default' |
| ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync' |
| |
| if len(target_args) > 0: |
| target_args.append('%(AdditionalOptions)') |
| ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args) |
| ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(target_inc_dirs) |
| target_defines.append('%(PreprocessorDefinitions)') |
| ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines) |
| ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true' |
| # Warning level |
| warning_level = T.cast('str', target.get_option(OptionKey('warning_level'))) |
| warning_level = 'EnableAllWarnings' if warning_level == 'everything' else 'Level' + str(1 + int(warning_level)) |
| ET.SubElement(clconf, 'WarningLevel').text = warning_level |
| if target.get_option(OptionKey('werror')): |
| ET.SubElement(clconf, 'TreatWarningAsError').text = 'true' |
| # Optimization flags |
| o_flags = split_o_flags_args(build_args) |
| if '/Ox' in o_flags: |
| ET.SubElement(clconf, 'Optimization').text = 'Full' |
| elif '/O2' in o_flags: |
| ET.SubElement(clconf, 'Optimization').text = 'MaxSpeed' |
| elif '/O1' in o_flags: |
| ET.SubElement(clconf, 'Optimization').text = 'MinSpace' |
| elif '/Od' in o_flags: |
| ET.SubElement(clconf, 'Optimization').text = 'Disabled' |
| if '/Oi' in o_flags: |
| ET.SubElement(clconf, 'IntrinsicFunctions').text = 'true' |
| if '/Ob1' in o_flags: |
| ET.SubElement(clconf, 'InlineFunctionExpansion').text = 'OnlyExplicitInline' |
| elif '/Ob2' in o_flags: |
| ET.SubElement(clconf, 'InlineFunctionExpansion').text = 'AnySuitable' |
| # Size-preserving flags |
| if '/Os' in o_flags or '/O1' in o_flags: |
| ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Size' |
| # Note: setting FavorSizeOrSpeed with clang-cl conflicts with /Od and can make debugging difficult, so don't. |
| elif '/Od' not in o_flags: |
| ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Speed' |
| # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default |
| self.generate_lang_standard_info(file_args, clconf) |
| |
| resourcecompile = ET.SubElement(compiles, 'ResourceCompile') |
| ET.SubElement(resourcecompile, 'PreprocessorDefinitions') |
| |
| # Linker options |
| link = ET.SubElement(compiles, 'Link') |
| extra_link_args = compiler.compiler_args() |
| extra_link_args += compiler.get_optimization_link_args(self.optimization) |
| # Generate Debug info |
| if self.debug: |
| self.generate_debug_information(link) |
| else: |
| ET.SubElement(link, 'GenerateDebugInformation').text = 'false' |
| if not isinstance(target, build.StaticLibrary): |
| if isinstance(target, build.SharedModule): |
| extra_link_args += compiler.get_std_shared_module_link_args(target.get_options()) |
| # Add link args added using add_project_link_arguments() |
| extra_link_args += self.build.get_project_link_args(compiler, target.subproject, target.for_machine) |
| # Add link args added using add_global_link_arguments() |
| # These override per-project link arguments |
| extra_link_args += self.build.get_global_link_args(compiler, target.for_machine) |
| # Link args added from the env: LDFLAGS, or the cross file. We want |
| # these to override all the defaults but not the per-target link |
| # args. |
| extra_link_args += self.environment.coredata.get_external_link_args( |
| target.for_machine, compiler.get_language()) |
| # Only non-static built targets need link args and link dependencies |
| extra_link_args += target.link_args |
| # External deps must be last because target link libraries may depend on them. |
| for dep in target.get_external_deps(): |
| # Extend without reordering or de-dup to preserve `-L -l` sets |
| # https://github.com/mesonbuild/meson/issues/1718 |
| if dep.name == 'openmp': |
| ET.SubElement(clconf, 'OpenMPSupport').text = 'true' |
| else: |
| extra_link_args.extend_direct(dep.get_link_args()) |
| for d in target.get_dependencies(): |
| if isinstance(d, build.StaticLibrary): |
| for dep in d.get_external_deps(): |
| if dep.name == 'openmp': |
| ET.SubElement(clconf, 'OpenMPSupport').text = 'true' |
| else: |
| extra_link_args.extend_direct(dep.get_link_args()) |
| # Add link args for c_* or cpp_* build options. Currently this only |
| # adds c_winlibs and cpp_winlibs when building for Windows. This needs |
| # to be after all internal and external libraries so that unresolved |
| # symbols from those can be found here. This is needed when the |
| # *_winlibs that we want to link to are static mingw64 libraries. |
| extra_link_args += compiler.get_option_link_args(target.get_options()) |
| (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native()) |
| |
| # Add more libraries to be linked if needed |
| for t in target.get_dependencies(): |
| if isinstance(t, build.CustomTargetIndex): |
| # We don't need the actual project here, just the library name |
| lobj = t |
| else: |
| lobj = self.build.targets[t.get_id()] |
| linkname = os.path.join(down, self.get_target_filename_for_linking(lobj)) |
| if t in target.link_whole_targets: |
| if compiler.id == 'msvc' and version_compare(compiler.version, '<19.00.23918'): |
| # Expand our object lists manually if we are on pre-Visual Studio 2015 Update 2 |
| l = t.extract_all_objects(False) |
| |
| # Unfortunately, we can't use self.object_filename_from_source() |
| for gen in l.genlist: |
| for src in gen.get_outputs(): |
| if self.environment.is_source(src): |
| path = self.get_target_generated_dir(t, gen, src) |
| gen_src_ext = '.' + os.path.splitext(path)[1][1:] |
| extra_link_args.append(path[:-len(gen_src_ext)] + '.obj') |
| |
| for src in l.srclist: |
| if self.environment.is_source(src): |
| target_private_dir = self.relpath(self.get_target_private_dir(t), |
| self.get_target_dir(t)) |
| rel_obj = self.object_filename_from_source(t, src, target_private_dir) |
| extra_link_args.append(rel_obj) |
| |
| extra_link_args.extend(self.flatten_object_list(t)) |
| else: |
| # /WHOLEARCHIVE:foo must go into AdditionalOptions |
| extra_link_args += compiler.get_link_whole_for(linkname) |
| # To force Visual Studio to build this project even though it |
| # has no sources, we include a reference to the vcxproj file |
| # that builds this target. Technically we should add this only |
| # if the current target has no sources, but it doesn't hurt to |
| # have 'extra' references. |
| trelpath = self.get_target_dir_relative_to(t, target) |
| tvcxproj = os.path.join(trelpath, t.get_id() + '.vcxproj') |
| tid = self.environment.coredata.target_guids[t.get_id()] |
| self.add_project_reference(root, tvcxproj, tid, link_outputs=True) |
| # Mark the dependency as already handled to not have |
| # multiple references to the same target. |
| self.handled_target_deps[target.get_id()].append(t.get_id()) |
| else: |
| # Other libraries go into AdditionalDependencies |
| if linkname not in additional_links: |
| additional_links.append(linkname) |
| for lib in self.get_custom_target_provided_libraries(target): |
| additional_links.append(self.relpath(lib, self.get_target_dir(target))) |
| |
| if len(extra_link_args) > 0: |
| extra_link_args.append('%(AdditionalOptions)') |
| ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args) |
| if len(additional_libpaths) > 0: |
| additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)') |
| ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths) |
| if len(additional_links) > 0: |
| additional_links.append('%(AdditionalDependencies)') |
| ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) |
| ofile = ET.SubElement(link, 'OutputFile') |
| ofile.text = f'$(OutDir){target.get_filename()}' |
| subsys = ET.SubElement(link, 'SubSystem') |
| subsys.text = subsystem |
| if isinstance(target, (build.SharedLibrary, build.Executable)) and target.get_import_filename(): |
| # DLLs built with MSVC always have an import library except when |
| # they're data-only DLLs, but we don't support those yet. |
| ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename() |
| if isinstance(target, (build.SharedLibrary, build.Executable)): |
| # Add module definitions file, if provided |
| if target.vs_module_defs: |
| relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src)) |
| ET.SubElement(link, 'ModuleDefinitionFile').text = relpath |
| if self.debug: |
| pdb = ET.SubElement(link, 'ProgramDataBaseFileName') |
| pdb.text = f'$(OutDir){target.name}.pdb' |
| targetmachine = ET.SubElement(link, 'TargetMachine') |
| if target.for_machine is MachineChoice.BUILD: |
| targetplatform = platform.lower() |
| else: |
| targetplatform = self.platform.lower() |
| if targetplatform == 'win32': |
| targetmachine.text = 'MachineX86' |
| elif targetplatform == 'x64' or detect_microsoft_gdk(targetplatform): |
| targetmachine.text = 'MachineX64' |
| elif targetplatform == 'arm': |
| targetmachine.text = 'MachineARM' |
| elif targetplatform == 'arm64': |
| targetmachine.text = 'MachineARM64' |
| elif targetplatform == 'arm64ec': |
| targetmachine.text = 'MachineARM64EC' |
| else: |
| raise MesonException('Unsupported Visual Studio target machine: ' + targetplatform) |
| # /nologo |
| ET.SubElement(link, 'SuppressStartupBanner').text = 'true' |
| # /release |
| if not target.get_option(OptionKey('debug')): |
| ET.SubElement(link, 'SetChecksum').text = 'true' |
| |
| # Visual studio doesn't simply allow the src files of a project to be added with the 'Condition=...' attribute, |
| # to allow us to point to the different debug/debugoptimized/release sets of generated src files for each of |
| # the solution's configurations. Similarly, 'ItemGroup' also doesn't support 'Condition'. So, without knowing |
| # a better (simple) alternative, for now, we'll repoint these generated sources (which will be incorrectly |
| # pointing to non-existent files under our '[builddir]_vs' directory) to the appropriate location under one of |
| # our buildtype build directores (e.g. '[builddir]_debug'). |
| # This will at least allow the user to open the files of generated sources listed in the solution explorer, |
| # once a build/compile has generated these sources. |
| # |
| # This modifies the paths in 'gen_files' in place, as opposed to returning a new list of modified paths. |
| def relocate_generated_file_paths_to_concrete_build_dir(self, gen_files: T.List[str], target: T.Union[build.Target, build.CustomTargetIndex]) -> None: |
| (_, build_dir_tail) = os.path.split(self.src_to_build) |
| meson_build_dir_for_buildtype = build_dir_tail[:-2] + coredata.get_genvs_default_buildtype_list()[0] # Get the first buildtype suffixed dir (i.e. '[builddir]_debug') from '[builddir]_vs' |
| # Relative path from this .vcxproj to the directory containing the set of '..._[debug/debugoptimized/release]' setup meson build dirs. |
| proj_to_build_root = self.target_to_build_root(target) |
| proj_to_multiconfigured_builds_parent_dir = os.path.join(proj_to_build_root, '..') |
| proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype)) |
| relocate_to_concrete_builddir_target = os.path.normpath(os.path.join(proj_to_build_dir_for_buildtype, self.get_target_dir(target))) |
| for idx, file_path in enumerate(gen_files): |
| gen_files[idx] = os.path.normpath(os.path.join(relocate_to_concrete_builddir_target, file_path)) |
| |
| # Returns bool indicating whether the .vcxproj has been generated. |
| # Under some circumstances, it's unnecessary to create some .vcxprojs, so, when generating the .sln, |
| # we need to respect that not all targets will have generated a project. |
| def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str, vslite_ctx: dict = None) -> bool: |
| mlog.debug(f'Generating vcxproj {target.name}.') |
| subsystem = 'Windows' |
| self.handled_target_deps[target.get_id()] = [] |
| |
| if self.gen_lite: |
| if not isinstance(target, build.BuildTarget): |
| # Since we're going to delegate all building to the one true meson build command, we don't need |
| # to generate .vcxprojs for targets that don't add any source files or just perform custom build |
| # commands. These are targets of types CustomTarget or RunTarget. So let's just skip generating |
| # these otherwise insubstantial non-BuildTarget targets. |
| return False |
| conftype = 'Makefile' |
| elif isinstance(target, build.Executable): |
| conftype = 'Application' |
| # If someone knows how to set the version properly, |
| # please send a patch. |
| subsystem = target.win_subsystem.split(',')[0] |
| elif isinstance(target, build.StaticLibrary): |
| conftype = 'StaticLibrary' |
| elif isinstance(target, build.SharedLibrary): |
| conftype = 'DynamicLibrary' |
| elif isinstance(target, build.CustomTarget): |
| self.gen_custom_target_vcxproj(target, ofname, guid) |
| return True |
| elif isinstance(target, build.RunTarget): |
| self.gen_run_target_vcxproj(target, ofname, guid) |
| return True |
| elif isinstance(target, build.CompileTarget): |
| self.gen_compile_target_vcxproj(target, ofname, guid) |
| return True |
| else: |
| raise MesonException(f'Unknown target type for {target.get_basename()}') |
| |
| (sources, headers, objects, _languages) = self.split_sources(target.sources) |
| if target.is_unity: |
| sources = self.generate_unity_files(target, sources) |
| if target.for_machine is MachineChoice.BUILD: |
| platform = self.build_platform |
| else: |
| platform = self.platform |
| |
| tfilename = os.path.splitext(target.get_filename()) |
| |
| (root, type_config) = self.create_basic_project(tfilename[0], |
| temp_dir=target.get_id(), |
| guid=guid, |
| conftype=conftype, |
| target_ext=tfilename[1], |
| target_platform=platform, |
| gen_manifest=self.get_gen_manifest(target)) |
| |
| generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands( |
| target, root) |
| (gen_src, gen_hdrs, gen_objs, _gen_langs) = self.split_sources(generated_files) |
| (custom_src, custom_hdrs, custom_objs, _custom_langs) = self.split_sources(custom_target_output_files) |
| gen_src += custom_src |
| gen_hdrs += custom_hdrs |
| |
| compiler = self._get_cl_compiler(target) |
| build_args = Vs2010Backend.get_build_args(compiler, self.optimization, self.debug, self.sanitize) |
| |
| assert isinstance(target, (build.Executable, build.SharedLibrary, build.StaticLibrary, build.SharedModule)), 'for mypy' |
| # Prefix to use to access the build root from the vcxproj dir |
| proj_to_build_root = self.target_to_build_root(target) |
| # Prefix to use to access the source tree's root from the vcxproj dir |
| proj_to_src_root = os.path.join(proj_to_build_root, self.build_to_src) |
| # Prefix to use to access the source tree's subdir from the vcxproj dir |
| proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target)) |
| |
| (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs) = self.get_args_defines_and_inc_dirs( |
| target, compiler, generated_files_include_dirs, proj_to_src_root, proj_to_src_dir, build_args) |
| |
| if self.gen_lite: |
| assert vslite_ctx is not None |
| primary_src_lang = get_primary_source_lang(target.sources, custom_src) |
| self.add_gen_lite_makefile_vcxproj_elements(root, platform, tfilename[1], vslite_ctx, target, proj_to_build_root, primary_src_lang) |
| else: |
| self.add_non_makefile_vcxproj_elements(root, type_config, target, platform, subsystem, build_args, target_args, target_defines, target_inc_dirs, file_args) |
| |
| meson_file_group = ET.SubElement(root, 'ItemGroup') |
| ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename)) |
| |
| # Visual Studio can't load projects that present duplicated items. Filter them out |
| # by keeping track of already added paths. |
| def path_normalize_add(path, lis): |
| normalized = os.path.normcase(os.path.normpath(path)) |
| if normalized not in lis: |
| lis.append(normalized) |
| return True |
| else: |
| return False |
| |
| pch_sources = {} |
| if self.target_uses_pch(target): |
| for lang in ['c', 'cpp']: |
| pch = target.get_pch(lang) |
| if not pch: |
| continue |
| if compiler.id == 'msvc': |
| if len(pch) == 1: |
| # Auto generate PCH. |
| src = os.path.join(proj_to_build_root, self.create_msvc_pch_implementation(target, lang, pch[0])) |
| pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0])) |
| else: |
| src = os.path.join(proj_to_src_dir, pch[1]) |
| pch_header_dir = None |
| pch_sources[lang] = [pch[0], src, lang, pch_header_dir] |
| else: |
| # I don't know whether its relevant but let's handle other compilers |
| # used with a vs backend |
| pch_sources[lang] = [pch[0], None, lang, None] |
| |
| previous_includes = [] |
| if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0: |
| if self.gen_lite and gen_hdrs: |
| # Although we're constructing our .vcxproj under our '..._vs' directory, we want to reference generated files |
| # in our concrete build directories (e.g. '..._debug'), where generated files will exist after building. |
| self.relocate_generated_file_paths_to_concrete_build_dir(gen_hdrs, target) |
| |
| inc_hdrs = ET.SubElement(root, 'ItemGroup') |
| for h in headers: |
| relpath = os.path.join(proj_to_build_root, h.rel_to_builddir(self.build_to_src)) |
| if path_normalize_add(relpath, previous_includes): |
| ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath) |
| for h in gen_hdrs: |
| if path_normalize_add(h, previous_includes): |
| ET.SubElement(inc_hdrs, 'CLInclude', Include=h) |
| for h in target.extra_files: |
| relpath = os.path.join(proj_to_build_root, h.rel_to_builddir(self.build_to_src)) |
| if path_normalize_add(relpath, previous_includes): |
| ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath) |
| for headers in pch_sources.values(): |
| path = os.path.join(proj_to_src_dir, headers[0]) |
| if path_normalize_add(path, previous_includes): |
| ET.SubElement(inc_hdrs, 'CLInclude', Include=path) |
| |
| previous_sources = [] |
| if len(sources) + len(gen_src) + len(pch_sources) > 0: |
| if self.gen_lite: |
| # Get data to fill in intellisense fields for sources that can't reference the project-wide values |
| defs_paths_opts_per_lang_and_buildtype = get_non_primary_lang_intellisense_fields( |
| vslite_ctx, |
| target.get_id(), |
| primary_src_lang) |
| if gen_src: |
| # Although we're constructing our .vcxproj under our '..._vs' directory, we want to reference generated files |
| # in our concrete build directories (e.g. '..._debug'), where generated files will exist after building. |
| self.relocate_generated_file_paths_to_concrete_build_dir(gen_src, target) |
| |
| inc_src = ET.SubElement(root, 'ItemGroup') |
| for s in sources: |
| relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src)) |
| if path_normalize_add(relpath, previous_sources): |
| inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath) |
| if self.gen_lite: |
| self.add_project_nmake_defs_incs_and_opts(inc_cl, relpath, defs_paths_opts_per_lang_and_buildtype, platform) |
| else: |
| lang = Vs2010Backend.lang_from_source_file(s) |
| self.add_pch(pch_sources, lang, inc_cl) |
| self.add_additional_options(lang, inc_cl, file_args) |
| self.add_preprocessor_defines(lang, inc_cl, file_defines) |
| self.add_include_dirs(lang, inc_cl, file_inc_dirs) |
| ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \ |
| self.object_filename_from_source(target, s) |
| for s in gen_src: |
| if path_normalize_add(s, previous_sources): |
| inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s) |
| if self.gen_lite: |
| self.add_project_nmake_defs_incs_and_opts(inc_cl, s, defs_paths_opts_per_lang_and_buildtype, platform) |
| else: |
| lang = Vs2010Backend.lang_from_source_file(s) |
| self.add_pch(pch_sources, lang, inc_cl) |
| self.add_additional_options(lang, inc_cl, file_args) |
| self.add_preprocessor_defines(lang, inc_cl, file_defines) |
| self.add_include_dirs(lang, inc_cl, file_inc_dirs) |
| s = File.from_built_file(target.get_subdir(), s) |
| ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \ |
| self.object_filename_from_source(target, s) |
| for lang, headers in pch_sources.items(): |
| impl = headers[1] |
| if impl and path_normalize_add(impl, previous_sources): |
| inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=impl) |
| self.create_pch(pch_sources, lang, inc_cl) |
| if self.gen_lite: |
| self.add_project_nmake_defs_incs_and_opts(inc_cl, impl, defs_paths_opts_per_lang_and_buildtype, platform) |
| else: |
| self.add_additional_options(lang, inc_cl, file_args) |
| self.add_preprocessor_defines(lang, inc_cl, file_defines) |
| pch_header_dir = pch_sources[lang][3] |
| if pch_header_dir: |
| inc_dirs = copy.deepcopy(file_inc_dirs) |
| inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang] |
| else: |
| inc_dirs = file_inc_dirs |
| self.add_include_dirs(lang, inc_cl, inc_dirs) |
| # XXX: Do we need to set the object file name here too? |
| |
| additional_objects = [] |
| for o in self.flatten_object_list(target, proj_to_build_root)[0]: |
| assert isinstance(o, str) |
| additional_objects.append(o) |
| for o in custom_objs: |
| additional_objects.append(o) |
| |
| # VS automatically links CustomBuild outputs whose name ends in .obj or .res, |
| # but the others need to be included explicitly |
| explicit_link_gen_objs = [obj for obj in gen_objs if not obj.endswith(('.obj', '.res'))] |
| |
| previous_objects = [] |
| if len(objects) + len(additional_objects) + len(explicit_link_gen_objs) > 0: |
| inc_objs = ET.SubElement(root, 'ItemGroup') |
| for s in objects: |
| relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src)) |
| if path_normalize_add(relpath, previous_objects): |
| ET.SubElement(inc_objs, 'Object', Include=relpath) |
| for s in additional_objects + explicit_link_gen_objs: |
| if path_normalize_add(s, previous_objects): |
| ET.SubElement(inc_objs, 'Object', Include=s) |
| |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| self.add_regen_dependency(root) |
| if not self.gen_lite: |
| # Injecting further target dependencies into this vcxproj implies and forces a Visual Studio BUILD dependency, |
| # which we don't want when using 'genvslite'. A gen_lite build as little involvement with the visual studio's |
| # build system as possible. |
| self.add_target_deps(root, target) |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': |
| self.gen_vcxproj_filters(target, ofname) |
| return True |
| |
| def gen_vcxproj_filters(self, target, ofname): |
| # Generate pitchfork of filters based on directory structure. |
| root = ET.Element('Project', {'ToolsVersion': '4.0', |
| 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}) |
| filter_folders = ET.SubElement(root, 'ItemGroup') |
| filter_items = ET.SubElement(root, 'ItemGroup') |
| mlog.debug(f'Generating vcxproj filters {target.name}.') |
| |
| def relative_to_defined_in(file): |
| # Get the relative path to file's directory from the location of the meson.build that defines this target. |
| return os.path.dirname(self.relpath(PureWindowsPath(file.subdir, file.fname), self.get_target_dir(target))) |
| |
| found_folders_to_filter = {} |
| all_files = target.sources + target.extra_files |
| |
| # Build a dictionary of all used relative paths (i.e. from the meson.build defining this target) |
| # for all sources. |
| for i in all_files: |
| if not os.path.isabs(i.fname): |
| dirname = relative_to_defined_in(i) |
| if dirname: |
| found_folders_to_filter[dirname] = '' |
| |
| # Now walk up each of those relative paths checking for empty intermediate dirs to generate the filter. |
| for folder in found_folders_to_filter: |
| dirname = folder |
| filter = '' |
| |
| while dirname: |
| basename = os.path.basename(dirname) |
| |
| if filter == '': |
| filter = basename |
| else: |
| # Use '/' to squash empty dirs. To actually get a '\', use '%255c'. |
| filter = basename + ('\\' if dirname in found_folders_to_filter else '/') + filter |
| |
| dirname = os.path.dirname(dirname) |
| |
| # Don't add an empty filter, breaks all other (?) filters. |
| if filter != '': |
| found_folders_to_filter[folder] = filter |
| filter_element = ET.SubElement(filter_folders, 'Filter', {'Include': filter}) |
| uuid_element = ET.SubElement(filter_element, 'UniqueIdentifier') |
| uuid_element.text = '{' + str(uuid.uuid4()).upper() + '}' |
| |
| sources, headers, objects, _ = self.split_sources(all_files) |
| down = self.target_to_build_root(target) |
| |
| def add_element(type_name, elements): |
| for i in elements: |
| if not os.path.isabs(i.fname): |
| dirname = relative_to_defined_in(i) |
| |
| if dirname and dirname in found_folders_to_filter: |
| relpath = os.path.join(down, i.rel_to_builddir(self.build_to_src)) |
| target_element = ET.SubElement(filter_items, type_name, {'Include': relpath}) |
| filter_element = ET.SubElement(target_element, 'Filter') |
| filter_element.text = found_folders_to_filter[dirname] |
| |
| add_element('ClCompile', sources) |
| add_element('ClInclude', headers) |
| add_element('Object', objects) |
| |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname + '.filters') |
| |
| def gen_regenproj(self): |
| # To fully adapt the REGEN work for a 'genvslite' solution, to check timestamps, settings, and regenerate the |
| # '[builddir]_vs' solution/vcxprojs, as well as regenerating the accompanying buildtype-suffixed ninja build |
| # directories (from which we need to first collect correct, updated preprocessor defs and compiler options in |
| # order to fill in the regenerated solution's intellisense settings) would require some non-trivial intrusion |
| # into the 'meson --internal regencheck ./meson-private' execution path (and perhaps also the '--internal |
| # regenerate' and even 'meson setup --reconfigure' code). So, for now, we'll instead give the user a simpler |
| # 'reconfigure' utility project that just runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on |
| # each of the ninja build dirs. |
| # |
| # FIXME: That will keep the building and compiling correctly configured but obviously won't update the |
| # solution and vcxprojs, which may allow solution src files and intellisense options to go out-of-date; the |
| # user would still have to manually 'meson setup --genvslite [vsxxxx] [builddir] [srcdir]' to fully regenerate |
| # a complete and correct solution. |
| if self.gen_lite: |
| project_name = 'RECONFIGURE' |
| ofname = os.path.join(self.environment.get_build_dir(), 'RECONFIGURE.vcxproj') |
| conftype = 'Makefile' |
| # I find the REGEN project doesn't work; it fails to invoke the appropriate - |
| # python meson.py --internal regencheck builddir\meson-private |
| # command, despite the fact that manually running such a command in a shell runs just fine. |
| # Running/building the regen project produces the error - |
| # ...Microsoft.CppBuild.targets(460,5): error MSB8020: The build tools for ClangCL (Platform Toolset = 'ClangCL') cannot be found. To build using the ClangCL build tools, please install ... |
| # Not sure why but a simple makefile-style project that executes the full '...regencheck...' command actually works (and seems a little simpler). |
| # Although I've limited this change to only happen under '--genvslite', perhaps ... |
| # FIXME : Should all utility projects use the simpler and less problematic makefile-style project? |
| else: |
| project_name = 'REGEN' |
| ofname = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj') |
| conftype = 'Utility' |
| |
| guid = self.environment.coredata.regen_guid |
| (root, type_config) = self.create_basic_project(project_name, |
| temp_dir='regen-temp', |
| guid=guid, |
| conftype=conftype |
| ) |
| |
| if self.gen_lite: |
| (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths() |
| all_configs_prop_group = ET.SubElement(root, 'PropertyGroup') |
| |
| # Multi-line command to reconfigure all buildtype-suffixed build dirs |
| multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() |
| (_, build_dir_tail) = os.path.split(self.src_to_build) |
| proj_to_multiconfigured_builds_parent_dir = '..' # We know this RECONFIGURE.vcxproj will always be in the '[buildir]_vs' dir. |
| proj_to_src_dir = self.build_to_src |
| reconfigure_all_cmd = '' |
| for buildtype in multi_config_buildtype_list: |
| meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example. |
| proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype)) |
| reconfigure_all_cmd += f'{nmake_base_meson_command} setup --reconfigure "{proj_to_build_dir_for_buildtype}" "{proj_to_src_dir}"\n' |
| ET.SubElement(all_configs_prop_group, 'NMakeBuildCommandLine').text = reconfigure_all_cmd |
| ET.SubElement(all_configs_prop_group, 'NMakeReBuildCommandLine').text = reconfigure_all_cmd |
| ET.SubElement(all_configs_prop_group, 'NMakeCleanCommandLine').text = '' |
| |
| #Need to set the 'ExecutablePath' element for the above NMake... commands to be able to execute |
| ET.SubElement(all_configs_prop_group, 'ExecutablePath').text = exe_search_paths |
| else: |
| action = ET.SubElement(root, 'ItemDefinitionGroup') |
| midl = ET.SubElement(action, 'Midl') |
| ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)' |
| ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)' |
| ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h' |
| ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb' |
| ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c' |
| ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' |
| regen_command = self.environment.get_build_command() + ['--internal', 'regencheck'] |
| cmd_templ = '''call %s > NUL |
| "%s" "%s"''' |
| regen_command = cmd_templ % \ |
| (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir()) |
| self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(), |
| outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())], |
| msg='Checking whether solution needs to be regenerated.') |
| |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets') |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| |
| def gen_testproj(self): |
| project_name = 'RUN_TESTS' |
| ofname = os.path.join(self.environment.get_build_dir(), f'{project_name}.vcxproj') |
| guid = self.environment.coredata.test_guid |
| if self.gen_lite: |
| (root, type_config) = self.create_basic_project(project_name, |
| temp_dir='install-temp', |
| guid=guid, |
| conftype='Makefile' |
| ) |
| (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths() |
| multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() |
| (_, build_dir_tail) = os.path.split(self.src_to_build) |
| proj_to_multiconfigured_builds_parent_dir = '..' # We know this .vcxproj will always be in the '[buildir]_vs' dir. |
| # Add appropriate 'test' commands for the 'build' action of this project, for all buildtypes |
| for buildtype in multi_config_buildtype_list: |
| meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example. |
| proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype)) |
| test_cmd = f'{nmake_base_meson_command} test -C "{proj_to_build_dir_for_buildtype}" --no-rebuild' |
| if not self.environment.coredata.get_option(OptionKey('stdsplit')): |
| test_cmd += ' --no-stdsplit' |
| if self.environment.coredata.get_option(OptionKey('errorlogs')): |
| test_cmd += ' --print-errorlogs' |
| condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{self.platform}\'' |
| prop_group = ET.SubElement(root, 'PropertyGroup', Condition=condition) |
| ET.SubElement(prop_group, 'NMakeBuildCommandLine').text = test_cmd |
| #Need to set the 'ExecutablePath' element for the NMake... commands to be able to execute |
| ET.SubElement(prop_group, 'ExecutablePath').text = exe_search_paths |
| else: |
| (root, type_config) = self.create_basic_project(project_name, |
| temp_dir='test-temp', |
| guid=guid) |
| |
| action = ET.SubElement(root, 'ItemDefinitionGroup') |
| midl = ET.SubElement(action, 'Midl') |
| ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)' |
| ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)' |
| ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h' |
| ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb' |
| ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c' |
| ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' |
| # FIXME: No benchmarks? |
| test_command = self.environment.get_build_command() + ['test', '--no-rebuild'] |
| if not self.environment.coredata.get_option(OptionKey('stdsplit')): |
| test_command += ['--no-stdsplit'] |
| if self.environment.coredata.get_option(OptionKey('errorlogs')): |
| test_command += ['--print-errorlogs'] |
| self.serialize_tests() |
| self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command))) |
| |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| self.add_regen_dependency(root) |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| |
| def gen_installproj(self): |
| project_name = 'RUN_INSTALL' |
| ofname = os.path.join(self.environment.get_build_dir(), f'{project_name}.vcxproj') |
| guid = self.environment.coredata.install_guid |
| if self.gen_lite: |
| (root, type_config) = self.create_basic_project(project_name, |
| temp_dir='install-temp', |
| guid=guid, |
| conftype='Makefile' |
| ) |
| (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths() |
| multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() |
| (_, build_dir_tail) = os.path.split(self.src_to_build) |
| proj_to_multiconfigured_builds_parent_dir = '..' # We know this .vcxproj will always be in the '[buildir]_vs' dir. |
| # Add appropriate 'install' commands for the 'build' action of this project, for all buildtypes |
| for buildtype in multi_config_buildtype_list: |
| meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example. |
| proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype)) |
| install_cmd = f'{nmake_base_meson_command} install -C "{proj_to_build_dir_for_buildtype}" --no-rebuild' |
| condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{self.platform}\'' |
| prop_group = ET.SubElement(root, 'PropertyGroup', Condition=condition) |
| ET.SubElement(prop_group, 'NMakeBuildCommandLine').text = install_cmd |
| #Need to set the 'ExecutablePath' element for the NMake... commands to be able to execute |
| ET.SubElement(prop_group, 'ExecutablePath').text = exe_search_paths |
| else: |
| self.create_install_data_files() |
| |
| (root, type_config) = self.create_basic_project(project_name, |
| temp_dir='install-temp', |
| guid=guid) |
| |
| action = ET.SubElement(root, 'ItemDefinitionGroup') |
| midl = ET.SubElement(action, 'Midl') |
| ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)' |
| ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)' |
| ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h' |
| ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb' |
| ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c' |
| ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' |
| install_command = self.environment.get_build_command() + ['install', '--no-rebuild'] |
| self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command))) |
| |
| ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') |
| self.add_regen_dependency(root) |
| self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname) |
| |
| def add_custom_build(self, node: ET.Element, rulename: str, command: str, deps: T.Optional[T.List[str]] = None, |
| outputs: T.Optional[T.List[str]] = None, msg: T.Optional[str] = None, verify_files: bool = True) -> None: |
| igroup = ET.SubElement(node, 'ItemGroup') |
| rulefile = os.path.join(self.environment.get_scratch_dir(), rulename + '.rule') |
| if not os.path.exists(rulefile): |
| with open(rulefile, 'w', encoding='utf-8') as f: |
| f.write("# Meson regen file.") |
| custombuild = ET.SubElement(igroup, 'CustomBuild', Include=rulefile) |
| if msg: |
| message = ET.SubElement(custombuild, 'Message') |
| message.text = msg |
| if not verify_files: |
| ET.SubElement(custombuild, 'VerifyInputsAndOutputsExist').text = 'false' |
| |
| # If a command ever were to change the current directory or set local |
| # variables this would need to be more complicated, as msbuild by |
| # default executes all CustomBuilds in a project using the same |
| # shell. Right now such tasks are all done inside the meson_exe |
| # wrapper. The trailing newline appears to be necessary to allow |
| # parallel custom builds to work. |
| ET.SubElement(custombuild, 'Command').text = f"{command}\n" |
| |
| if not outputs: |
| # Use a nonexistent file to always consider the target out-of-date. |
| outputs = [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(), |
| 'outofdate.file'))] |
| ET.SubElement(custombuild, 'Outputs').text = ';'.join(outputs) |
| if deps: |
| ET.SubElement(custombuild, 'AdditionalInputs').text = ';'.join(deps) |
| |
| @staticmethod |
| def nonexistent_file(prefix: str) -> str: |
| i = 0 |
| file = prefix |
| while os.path.exists(file): |
| file = '%s%d' % (prefix, i) |
| return file |
| |
| def generate_debug_information(self, link: ET.Element) -> None: |
| # valid values for vs2015 is 'false', 'true', 'DebugFastLink' |
| ET.SubElement(link, 'GenerateDebugInformation').text = 'true' |
| |
| def add_regen_dependency(self, root: ET.Element) -> None: |
| # For now, with 'genvslite' solutions, REGEN is replaced by the lighter-weight RECONFIGURE utility that is |
| # no longer a forced build dependency. See comment in 'gen_regenproj' |
| if not self.gen_lite: |
| regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj') |
| self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid) |
| |
| def generate_lang_standard_info(self, file_args: T.Dict[str, CompilerArgs], clconf: ET.Element) -> None: |
| pass |
| |
| # Returns if a target generates a manifest or not. |
| def get_gen_manifest(self, target): |
| if not isinstance(target, build.BuildTarget): |
| return True |
| |
| compiler = self._get_cl_compiler(target) |
| link_args = compiler.compiler_args() |
| if not isinstance(target, build.StaticLibrary): |
| link_args += self.build.get_project_link_args(compiler, target.subproject, target.for_machine) |
| link_args += self.build.get_global_link_args(compiler, target.for_machine) |
| link_args += self.environment.coredata.get_external_link_args( |
| target.for_machine, compiler.get_language()) |
| link_args += target.link_args |
| |
| for arg in reversed(link_args): |
| arg = arg.upper() |
| if arg == '/MANIFEST:NO': |
| return False |
| if arg == '/MANIFEST' or arg.startswith('/MANIFEST:'): |
| break |
| return True |