From b298a9f968429f321dca088c711b818f2c094ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Sat, 23 May 2020 13:14:54 +0200 Subject: [PATCH] feat: port "add support for MSVC cross-compilation" from node Original commit message: tools,gyp: add support for MSVC cross-compilation This change means that GYP can now generate two sets of projects: one exclusively for a host x64 machine and one containing a mix of x64 and Arm targets. The names of host targets are fixed up to end with _host.exe, and any actions involving them are fixed up. This allows compilation of Node on an x64 server for a Windows on Arm target. Refs: https://github.com/nodejs/node/pull/32867 Closes: https://github.com/nodejs/gyp-next/issues/40 --- pylib/gyp/generator/msvs.py | 98 +++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/pylib/gyp/generator/msvs.py b/pylib/gyp/generator/msvs.py index 3a8aac0e..7462b7b4 100644 --- a/pylib/gyp/generator/msvs.py +++ b/pylib/gyp/generator/msvs.py @@ -39,6 +39,7 @@ # letters. VALID_MSVS_GUID_CHARS = re.compile(r"^[A-F0-9\-]+$") +generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() generator_default_variables = { "DRIVER_PREFIX": "", @@ -50,7 +51,7 @@ "STATIC_LIB_SUFFIX": ".lib", "SHARED_LIB_SUFFIX": ".dll", "INTERMEDIATE_DIR": "$(IntDir)", - "SHARED_INTERMEDIATE_DIR": "$(OutDir)obj/global_intermediate", + "SHARED_INTERMEDIATE_DIR": "$(OutDir)/obj/global_intermediate", "OS": "win", "PRODUCT_DIR": "$(OutDir)", "LIB_DIR": "$(OutDir)lib", @@ -1005,7 +1006,7 @@ def _GetMsbuildToolsetOfProject(proj_path, spec, version): return toolset -def _GenerateProject(project, options, version, generator_flags): +def _GenerateProject(project, options, version, generator_flags, spec): """Generates a vcproj file. Arguments: @@ -1023,7 +1024,7 @@ def _GenerateProject(project, options, version, generator_flags): return [] if version.UsesVcxproj(): - return _GenerateMSBuildProject(project, options, version, generator_flags) + return _GenerateMSBuildProject(project, options, version, generator_flags, spec) else: return _GenerateMSVSProject(project, options, version, generator_flags) @@ -1903,6 +1904,8 @@ def _GatherSolutionFolders(sln_projects, project_objects, flat): # Convert into a tree of dicts on path. for p in sln_projects: gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2] + if p.endswith("#host"): + target += "_host" gyp_dir = os.path.dirname(gyp_file) path_dict = _GetPathDict(root, gyp_dir) path_dict[target + ".vcproj"] = project_objects[p] @@ -1921,9 +1924,10 @@ def _GetPathOfProject(qualified_target, spec, options, msvs_version): default_config = _GetDefaultConfiguration(spec) proj_filename = default_config.get("msvs_existing_vcproj") if not proj_filename: - proj_filename = ( - spec["target_name"] + options.suffix + msvs_version.ProjectExtension() - ) + proj_filename = spec["target_name"] + if spec["toolset"] == "host": + proj_filename += "_host" + proj_filename = proj_filename + options.suffix + msvs_version.ProjectExtension() build_file = gyp.common.BuildFile(qualified_target) proj_path = os.path.join(os.path.dirname(build_file), proj_filename) @@ -1948,6 +1952,8 @@ def _GetPlatformOverridesOfProject(spec): _ConfigBaseName(config_name, _ConfigPlatform(c)), platform, ) + if spec["toolset"] == "host" and generator_supports_multiple_toolsets: + fixed_config_fullname = "%s|x64" % (config_name,) config_platform_overrides[config_fullname] = fixed_config_fullname return config_platform_overrides @@ -1968,11 +1974,6 @@ def _CreateProjectObjects(target_list, target_dicts, options, msvs_version): projects = {} for qualified_target in target_list: spec = target_dicts[qualified_target] - if spec["toolset"] != "target": - raise GypError( - "Multiple toolsets not supported in msvs build (target %s)" - % qualified_target - ) proj_path, fixpath_prefix = _GetPathOfProject( qualified_target, spec, options, msvs_version ) @@ -1980,9 +1981,12 @@ def _CreateProjectObjects(target_list, target_dicts, options, msvs_version): overrides = _GetPlatformOverridesOfProject(spec) build_file = gyp.common.BuildFile(qualified_target) # Create object for this project. + target_name = spec["target_name"] + if spec["toolset"] == "host": + target_name += "_host" obj = MSVSNew.MSVSProject( proj_path, - name=spec["target_name"], + name=target_name, guid=guid, spec=spec, build_file=build_file, @@ -2161,7 +2165,10 @@ def GenerateOutput(target_list, target_dicts, data, params): for qualified_target in target_list: spec = target_dicts[qualified_target] for config_name, config in spec["configurations"].items(): - configs.add(_ConfigFullName(config_name, config)) + config_name = _ConfigFullName(config_name, config) + configs.add(config_name) + if config_name == "Release|arm64": + configs.add("Release|x64") configs = list(configs) # Figure out all the projects that will be generated and their guids @@ -2174,12 +2181,15 @@ def GenerateOutput(target_list, target_dicts, data, params): for project in project_objects.values(): fixpath_prefix = project.fixpath_prefix missing_sources.extend( - _GenerateProject(project, options, msvs_version, generator_flags) + _GenerateProject(project, options, msvs_version, generator_flags, spec) ) fixpath_prefix = None for build_file in data: # Validate build_file extension + target_only_configs = configs + if generator_supports_multiple_toolsets: + target_only_configs = [i for i in configs if i.endswith("arm64")] if not build_file.endswith(".gyp"): continue sln_path = os.path.splitext(build_file)[0] + options.suffix + ".sln" @@ -2196,7 +2206,7 @@ def GenerateOutput(target_list, target_dicts, data, params): sln = MSVSNew.MSVSSolution( sln_path, entries=root_entries, - variants=configs, + variants=target_only_configs, websiteProperties=False, version=msvs_version, ) @@ -2930,22 +2940,24 @@ def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules): easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True) -def _GetConfigurationAndPlatform(name, settings): +def _GetConfigurationAndPlatform(name, settings, spec): configuration = name.rsplit("_", 1)[0] platform = settings.get("msvs_configuration_platform", "Win32") + if spec["toolset"] == "host" and platform == "arm64": + platform = "x64" # Host-only tools are always built for x64 return (configuration, platform) -def _GetConfigurationCondition(name, settings): +def _GetConfigurationCondition(name, settings, spec): return r"'$(Configuration)|$(Platform)'=='%s|%s'" % _GetConfigurationAndPlatform( - name, settings + name, settings, spec ) -def _GetMSBuildProjectConfigurations(configurations): +def _GetMSBuildProjectConfigurations(configurations, spec): group = ["ItemGroup", {"Label": "ProjectConfigurations"}] for (name, settings) in sorted(configurations.items()): - configuration, platform = _GetConfigurationAndPlatform(name, settings) + configuration, platform = _GetConfigurationAndPlatform(name, settings, spec) designation = "%s|%s" % (configuration, platform) group.append( [ @@ -3033,7 +3045,7 @@ def _GetMSBuildConfigurationDetails(spec, build_file): properties = {} for name, settings in spec["configurations"].items(): msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file) - condition = _GetConfigurationCondition(name, settings) + condition = _GetConfigurationCondition(name, settings, spec) character_set = msbuild_attributes.get("CharacterSet") config_type = msbuild_attributes.get("ConfigurationType") _AddConditionalProperty(properties, condition, "ConfigurationType", config_type) @@ -3064,12 +3076,12 @@ def _GetMSBuildLocalProperties(msbuild_toolset): return properties -def _GetMSBuildPropertySheets(configurations): +def _GetMSBuildPropertySheets(configurations, spec): user_props = r"$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" additional_props = {} props_specified = False for name, settings in sorted(configurations.items()): - configuration = _GetConfigurationCondition(name, settings) + configuration = _GetConfigurationCondition(name, settings, spec) if "msbuild_props" in settings: additional_props[configuration] = _FixPaths(settings["msbuild_props"]) props_specified = True @@ -3222,7 +3234,7 @@ def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file): properties = {} for (name, configuration) in sorted(configurations.items()): - condition = _GetConfigurationCondition(name, configuration) + condition = _GetConfigurationCondition(name, configuration, spec) attributes = _GetMSBuildAttributes(spec, configuration, build_file) msbuild_settings = configuration["finalized_msbuild_settings"] _AddConditionalProperty( @@ -3345,7 +3357,7 @@ def _GetMSBuildToolSettingsSections(spec, configurations): msbuild_settings = configuration["finalized_msbuild_settings"] group = [ "ItemDefinitionGroup", - {"Condition": _GetConfigurationCondition(name, configuration)}, + {"Condition": _GetConfigurationCondition(name, configuration, spec)}, ] for tool_name, tool_settings in sorted(msbuild_settings.items()): # Skip the tool named '' which is a holder of global settings handled @@ -3625,7 +3637,7 @@ def _AddSources2( if precompiled_source == source: condition = _GetConfigurationCondition( - config_name, configuration + config_name, configuration, spec ) detail.append( ["PrecompiledHeader", {"Condition": condition}, "Create"] @@ -3652,7 +3664,21 @@ def _GetMSBuildProjectReferences(project): references = [] if project.dependencies: group = ["ItemGroup"] + added_dependency_set = set() for dependency in project.dependencies: + dependency_spec = dependency.spec + should_skip_dep = False + if project.spec["toolset"] == "target": + if dependency_spec["toolset"] == "host": + if dependency_spec["type"] == "static_library": + should_skip_dep = True + if dependency.name.startswith("run_"): + should_skip_dep = False + if should_skip_dep: + continue + + canonical_name = dependency.name.replace("_host", "") + added_dependency_set.add(canonical_name) guid = dependency.guid project_dir = os.path.split(project.path)[0] relative_path = gyp.common.RelativePath(dependency.path, project_dir) @@ -3675,7 +3701,7 @@ def _GetMSBuildProjectReferences(project): return references -def _GenerateMSBuildProject(project, options, version, generator_flags): +def _GenerateMSBuildProject(project, options, version, generator_flags, spec): spec = project.spec configurations = spec["configurations"] project_dir, project_file_name = os.path.split(project.path) @@ -3774,7 +3800,7 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): }, ] - content += _GetMSBuildProjectConfigurations(configurations) + content += _GetMSBuildProjectConfigurations(configurations, spec) content += _GetMSBuildGlobalProperties( spec, version, project.guid, project_file_name ) @@ -3786,10 +3812,10 @@ def _GenerateMSBuildProject(project, options, version, generator_flags): content += _GetMSBuildLocalProperties(project.msbuild_toolset) content += import_cpp_props_section content += import_masm_props_section - if spec.get("msvs_enable_marmasm"): + if spec.get("msvs_enable_marmasm") or True: content += import_marmasm_props_section content += _GetMSBuildExtensions(props_files_of_rules) - content += _GetMSBuildPropertySheets(configurations) + content += _GetMSBuildPropertySheets(configurations, spec) content += macro_section content += _GetMSBuildConfigurationGlobalProperties( spec, configurations, project.build_file @@ -3893,15 +3919,27 @@ def _GenerateActionsForMSBuild(spec, actions_to_add): sources_handled_by_action = OrderedSet() actions_spec = [] for primary_input, actions in actions_to_add.items(): + if generator_supports_multiple_toolsets: + primary_input = primary_input.replace(".exe", "_host.exe") inputs = OrderedSet() outputs = OrderedSet() descriptions = [] commands = [] for action in actions: + + def fixup_host_exe(i): + if "$(OutDir)" in i: + i = i.replace(".exe", "_host.exe") + return i + + if generator_supports_multiple_toolsets: + action["inputs"] = [fixup_host_exe(i) for i in action["inputs"]] inputs.update(OrderedSet(action["inputs"])) outputs.update(OrderedSet(action["outputs"])) descriptions.append(action["description"]) cmd = action["command"] + if generator_supports_multiple_toolsets: + cmd = cmd.replace(".exe", "_host.exe") # For most actions, add 'call' so that actions that invoke batch files # return and continue executing. msbuild_use_call provides a way to # disable this but I have not seen any adverse effect from doing that