Skip to content

Commit 72ea11b

Browse files
targosryzokuken
authored andcommitted
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: nodejs/node#32867 Closes: #40
1 parent 44e3f3e commit 72ea11b

File tree

1 file changed

+68
-30
lines changed

1 file changed

+68
-30
lines changed

pylib/gyp/generator/msvs.py

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
# letters.
4040
VALID_MSVS_GUID_CHARS = re.compile(r"^[A-F0-9\-]+$")
4141

42+
generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
4243

4344
generator_default_variables = {
4445
"DRIVER_PREFIX": "",
@@ -50,7 +51,7 @@
5051
"STATIC_LIB_SUFFIX": ".lib",
5152
"SHARED_LIB_SUFFIX": ".dll",
5253
"INTERMEDIATE_DIR": "$(IntDir)",
53-
"SHARED_INTERMEDIATE_DIR": "$(OutDir)obj/global_intermediate",
54+
"SHARED_INTERMEDIATE_DIR": "$(OutDir)/obj/global_intermediate",
5455
"OS": "win",
5556
"PRODUCT_DIR": "$(OutDir)",
5657
"LIB_DIR": "$(OutDir)lib",
@@ -1005,7 +1006,7 @@ def _GetMsbuildToolsetOfProject(proj_path, spec, version):
10051006
return toolset
10061007

10071008

1008-
def _GenerateProject(project, options, version, generator_flags):
1009+
def _GenerateProject(project, options, version, generator_flags, spec):
10091010
"""Generates a vcproj file.
10101011
10111012
Arguments:
@@ -1023,7 +1024,7 @@ def _GenerateProject(project, options, version, generator_flags):
10231024
return []
10241025

10251026
if version.UsesVcxproj():
1026-
return _GenerateMSBuildProject(project, options, version, generator_flags)
1027+
return _GenerateMSBuildProject(project, options, version, generator_flags, spec)
10271028
else:
10281029
return _GenerateMSVSProject(project, options, version, generator_flags)
10291030

@@ -1903,6 +1904,8 @@ def _GatherSolutionFolders(sln_projects, project_objects, flat):
19031904
# Convert into a tree of dicts on path.
19041905
for p in sln_projects:
19051906
gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2]
1907+
if p.endswith("#host"):
1908+
target += "_host"
19061909
gyp_dir = os.path.dirname(gyp_file)
19071910
path_dict = _GetPathDict(root, gyp_dir)
19081911
path_dict[target + ".vcproj"] = project_objects[p]
@@ -1921,9 +1924,10 @@ def _GetPathOfProject(qualified_target, spec, options, msvs_version):
19211924
default_config = _GetDefaultConfiguration(spec)
19221925
proj_filename = default_config.get("msvs_existing_vcproj")
19231926
if not proj_filename:
1924-
proj_filename = (
1925-
spec["target_name"] + options.suffix + msvs_version.ProjectExtension()
1926-
)
1927+
proj_filename = spec["target_name"]
1928+
if spec["toolset"] == "host":
1929+
proj_filename += "_host"
1930+
proj_filename = proj_filename + options.suffix + msvs_version.ProjectExtension()
19271931

19281932
build_file = gyp.common.BuildFile(qualified_target)
19291933
proj_path = os.path.join(os.path.dirname(build_file), proj_filename)
@@ -1948,6 +1952,8 @@ def _GetPlatformOverridesOfProject(spec):
19481952
_ConfigBaseName(config_name, _ConfigPlatform(c)),
19491953
platform,
19501954
)
1955+
if spec["toolset"] == "host" and generator_supports_multiple_toolsets:
1956+
fixed_config_fullname = "%s|x64" % (config_name,)
19511957
config_platform_overrides[config_fullname] = fixed_config_fullname
19521958
return config_platform_overrides
19531959

@@ -1968,21 +1974,19 @@ def _CreateProjectObjects(target_list, target_dicts, options, msvs_version):
19681974
projects = {}
19691975
for qualified_target in target_list:
19701976
spec = target_dicts[qualified_target]
1971-
if spec["toolset"] != "target":
1972-
raise GypError(
1973-
"Multiple toolsets not supported in msvs build (target %s)"
1974-
% qualified_target
1975-
)
19761977
proj_path, fixpath_prefix = _GetPathOfProject(
19771978
qualified_target, spec, options, msvs_version
19781979
)
19791980
guid = _GetGuidOfProject(proj_path, spec)
19801981
overrides = _GetPlatformOverridesOfProject(spec)
19811982
build_file = gyp.common.BuildFile(qualified_target)
19821983
# Create object for this project.
1984+
target_name = spec["target_name"]
1985+
if spec["toolset"] == "host":
1986+
target_name += "_host"
19831987
obj = MSVSNew.MSVSProject(
19841988
proj_path,
1985-
name=spec["target_name"],
1989+
name=target_name,
19861990
guid=guid,
19871991
spec=spec,
19881992
build_file=build_file,
@@ -2161,7 +2165,10 @@ def GenerateOutput(target_list, target_dicts, data, params):
21612165
for qualified_target in target_list:
21622166
spec = target_dicts[qualified_target]
21632167
for config_name, config in spec["configurations"].items():
2164-
configs.add(_ConfigFullName(config_name, config))
2168+
config_name = _ConfigFullName(config_name, config)
2169+
configs.add(config_name)
2170+
if config_name == "Release|arm64":
2171+
configs.add("Release|x64")
21652172
configs = list(configs)
21662173

21672174
# Figure out all the projects that will be generated and their guids
@@ -2174,12 +2181,15 @@ def GenerateOutput(target_list, target_dicts, data, params):
21742181
for project in project_objects.values():
21752182
fixpath_prefix = project.fixpath_prefix
21762183
missing_sources.extend(
2177-
_GenerateProject(project, options, msvs_version, generator_flags)
2184+
_GenerateProject(project, options, msvs_version, generator_flags, spec)
21782185
)
21792186
fixpath_prefix = None
21802187

21812188
for build_file in data:
21822189
# Validate build_file extension
2190+
target_only_configs = configs
2191+
if generator_supports_multiple_toolsets:
2192+
target_only_configs = [i for i in configs if i.endswith("arm64")]
21832193
if not build_file.endswith(".gyp"):
21842194
continue
21852195
sln_path = os.path.splitext(build_file)[0] + options.suffix + ".sln"
@@ -2196,7 +2206,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
21962206
sln = MSVSNew.MSVSSolution(
21972207
sln_path,
21982208
entries=root_entries,
2199-
variants=configs,
2209+
variants=target_only_configs,
22002210
websiteProperties=False,
22012211
version=msvs_version,
22022212
)
@@ -2930,22 +2940,24 @@ def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules):
29302940
easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True)
29312941

29322942

2933-
def _GetConfigurationAndPlatform(name, settings):
2943+
def _GetConfigurationAndPlatform(name, settings, spec):
29342944
configuration = name.rsplit("_", 1)[0]
29352945
platform = settings.get("msvs_configuration_platform", "Win32")
2946+
if spec["toolset"] == "host" and platform == "arm64":
2947+
platform = "x64" # Host-only tools are always built for x64
29362948
return (configuration, platform)
29372949

29382950

2939-
def _GetConfigurationCondition(name, settings):
2951+
def _GetConfigurationCondition(name, settings, spec):
29402952
return r"'$(Configuration)|$(Platform)'=='%s|%s'" % _GetConfigurationAndPlatform(
2941-
name, settings
2953+
name, settings, spec
29422954
)
29432955

29442956

2945-
def _GetMSBuildProjectConfigurations(configurations):
2957+
def _GetMSBuildProjectConfigurations(configurations, spec):
29462958
group = ["ItemGroup", {"Label": "ProjectConfigurations"}]
29472959
for (name, settings) in sorted(configurations.items()):
2948-
configuration, platform = _GetConfigurationAndPlatform(name, settings)
2960+
configuration, platform = _GetConfigurationAndPlatform(name, settings, spec)
29492961
designation = "%s|%s" % (configuration, platform)
29502962
group.append(
29512963
[
@@ -3033,7 +3045,7 @@ def _GetMSBuildConfigurationDetails(spec, build_file):
30333045
properties = {}
30343046
for name, settings in spec["configurations"].items():
30353047
msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file)
3036-
condition = _GetConfigurationCondition(name, settings)
3048+
condition = _GetConfigurationCondition(name, settings, spec)
30373049
character_set = msbuild_attributes.get("CharacterSet")
30383050
config_type = msbuild_attributes.get("ConfigurationType")
30393051
_AddConditionalProperty(properties, condition, "ConfigurationType", config_type)
@@ -3064,12 +3076,12 @@ def _GetMSBuildLocalProperties(msbuild_toolset):
30643076
return properties
30653077

30663078

3067-
def _GetMSBuildPropertySheets(configurations):
3079+
def _GetMSBuildPropertySheets(configurations, spec):
30683080
user_props = r"$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
30693081
additional_props = {}
30703082
props_specified = False
30713083
for name, settings in sorted(configurations.items()):
3072-
configuration = _GetConfigurationCondition(name, settings)
3084+
configuration = _GetConfigurationCondition(name, settings, spec)
30733085
if "msbuild_props" in settings:
30743086
additional_props[configuration] = _FixPaths(settings["msbuild_props"])
30753087
props_specified = True
@@ -3222,7 +3234,7 @@ def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file):
32223234

32233235
properties = {}
32243236
for (name, configuration) in sorted(configurations.items()):
3225-
condition = _GetConfigurationCondition(name, configuration)
3237+
condition = _GetConfigurationCondition(name, configuration, spec)
32263238
attributes = _GetMSBuildAttributes(spec, configuration, build_file)
32273239
msbuild_settings = configuration["finalized_msbuild_settings"]
32283240
_AddConditionalProperty(
@@ -3345,7 +3357,7 @@ def _GetMSBuildToolSettingsSections(spec, configurations):
33453357
msbuild_settings = configuration["finalized_msbuild_settings"]
33463358
group = [
33473359
"ItemDefinitionGroup",
3348-
{"Condition": _GetConfigurationCondition(name, configuration)},
3360+
{"Condition": _GetConfigurationCondition(name, configuration, spec)},
33493361
]
33503362
for tool_name, tool_settings in sorted(msbuild_settings.items()):
33513363
# Skip the tool named '' which is a holder of global settings handled
@@ -3625,7 +3637,7 @@ def _AddSources2(
36253637

36263638
if precompiled_source == source:
36273639
condition = _GetConfigurationCondition(
3628-
config_name, configuration
3640+
config_name, configuration, spec
36293641
)
36303642
detail.append(
36313643
["PrecompiledHeader", {"Condition": condition}, "Create"]
@@ -3652,7 +3664,21 @@ def _GetMSBuildProjectReferences(project):
36523664
references = []
36533665
if project.dependencies:
36543666
group = ["ItemGroup"]
3667+
added_dependency_set = set()
36553668
for dependency in project.dependencies:
3669+
dependency_spec = dependency.spec
3670+
should_skip_dep = False
3671+
if project.spec["toolset"] == "target":
3672+
if dependency_spec["toolset"] == "host":
3673+
if dependency_spec["type"] == "static_library":
3674+
should_skip_dep = True
3675+
if dependency.name.startswith("run_"):
3676+
should_skip_dep = False
3677+
if should_skip_dep:
3678+
continue
3679+
3680+
canonical_name = dependency.name.replace("_host", "")
3681+
added_dependency_set.add(canonical_name)
36563682
guid = dependency.guid
36573683
project_dir = os.path.split(project.path)[0]
36583684
relative_path = gyp.common.RelativePath(dependency.path, project_dir)
@@ -3675,7 +3701,7 @@ def _GetMSBuildProjectReferences(project):
36753701
return references
36763702

36773703

3678-
def _GenerateMSBuildProject(project, options, version, generator_flags):
3704+
def _GenerateMSBuildProject(project, options, version, generator_flags, spec):
36793705
spec = project.spec
36803706
configurations = spec["configurations"]
36813707
project_dir, project_file_name = os.path.split(project.path)
@@ -3774,7 +3800,7 @@ def _GenerateMSBuildProject(project, options, version, generator_flags):
37743800
},
37753801
]
37763802

3777-
content += _GetMSBuildProjectConfigurations(configurations)
3803+
content += _GetMSBuildProjectConfigurations(configurations, spec)
37783804
content += _GetMSBuildGlobalProperties(
37793805
spec, version, project.guid, project_file_name
37803806
)
@@ -3786,10 +3812,10 @@ def _GenerateMSBuildProject(project, options, version, generator_flags):
37863812
content += _GetMSBuildLocalProperties(project.msbuild_toolset)
37873813
content += import_cpp_props_section
37883814
content += import_masm_props_section
3789-
if spec.get("msvs_enable_marmasm"):
3815+
if spec.get("msvs_enable_marmasm") or True:
37903816
content += import_marmasm_props_section
37913817
content += _GetMSBuildExtensions(props_files_of_rules)
3792-
content += _GetMSBuildPropertySheets(configurations)
3818+
content += _GetMSBuildPropertySheets(configurations, spec)
37933819
content += macro_section
37943820
content += _GetMSBuildConfigurationGlobalProperties(
37953821
spec, configurations, project.build_file
@@ -3893,15 +3919,27 @@ def _GenerateActionsForMSBuild(spec, actions_to_add):
38933919
sources_handled_by_action = OrderedSet()
38943920
actions_spec = []
38953921
for primary_input, actions in actions_to_add.items():
3922+
if generator_supports_multiple_toolsets:
3923+
primary_input = primary_input.replace(".exe", "_host.exe")
38963924
inputs = OrderedSet()
38973925
outputs = OrderedSet()
38983926
descriptions = []
38993927
commands = []
39003928
for action in actions:
3929+
3930+
def fixup_host_exe(i):
3931+
if "$(OutDir)" in i:
3932+
i = i.replace(".exe", "_host.exe")
3933+
return i
3934+
3935+
if generator_supports_multiple_toolsets:
3936+
action["inputs"] = [fixup_host_exe(i) for i in action["inputs"]]
39013937
inputs.update(OrderedSet(action["inputs"]))
39023938
outputs.update(OrderedSet(action["outputs"]))
39033939
descriptions.append(action["description"])
39043940
cmd = action["command"]
3941+
if generator_supports_multiple_toolsets:
3942+
cmd = cmd.replace(".exe", "_host.exe")
39053943
# For most actions, add 'call' so that actions that invoke batch files
39063944
# return and continue executing. msbuild_use_call provides a way to
39073945
# disable this but I have not seen any adverse effect from doing that

0 commit comments

Comments
 (0)