update electron to v43
All checks were successful
Android Build / publish (push) Successful in 55s
Linux Build / publish (push) Successful in 1m6s

This commit is contained in:
olcxja 2026-07-09 22:38:33 +02:00
commit fb6c8b6ee9
5385 changed files with 513060 additions and 123058 deletions

View file

@ -32,20 +32,20 @@ ENTRY_TYPE_GUIDS = {
def MakeGuid(name, seed="msvs_new"):
"""Returns a GUID for the specified target name.
Args:
name: Target name.
seed: Seed for MD5 hash.
Returns:
A GUID-line string calculated from the name and seed.
Args:
name: Target name.
seed: Seed for SHA-256 hash.
Returns:
A GUID-line string calculated from the name and seed.
This generates something which looks like a GUID, but depends only on the
name and seed. This means the same name/seed will always generate the same
GUID, so that projects and solutions which refer to each other can explicitly
determine the GUID to refer to explicitly. It also means that the GUID will
not change when the project for a target is rebuilt.
"""
# Calculate a MD5 signature for the seed and name.
d = hashlib.md5((str(seed) + str(name)).encode("utf-8")).hexdigest().upper()
This generates something which looks like a GUID, but depends only on the
name and seed. This means the same name/seed will always generate the same
GUID, so that projects and solutions which refer to each other can explicitly
determine the GUID to refer to explicitly. It also means that the GUID will
not change when the project for a target is rebuilt.
"""
# Calculate a SHA-256 signature for the seed and name.
d = hashlib.sha256((str(seed) + str(name)).encode("utf-8")).hexdigest().upper()
# Convert most of the signature to GUID form (discard the rest)
guid = (
"{"
@ -78,15 +78,15 @@ class MSVSFolder(MSVSSolutionEntry):
def __init__(self, path, name=None, entries=None, guid=None, items=None):
"""Initializes the folder.
Args:
path: Full path to the folder.
name: Name of the folder.
entries: List of folder entries to nest inside this folder. May contain
Folder or Project objects. May be None, if the folder is empty.
guid: GUID to use for folder, if not None.
items: List of solution items to include in the folder project. May be
None, if the folder does not directly contain items.
"""
Args:
path: Full path to the folder.
name: Name of the folder.
entries: List of folder entries to nest inside this folder. May contain
Folder or Project objects. May be None, if the folder is empty.
guid: GUID to use for folder, if not None.
items: List of solution items to include in the folder project. May be
None, if the folder does not directly contain items.
"""
if name:
self.name = name
else:
@ -128,19 +128,19 @@ class MSVSProject(MSVSSolutionEntry):
):
"""Initializes the project.
Args:
path: Absolute path to the project file.
name: Name of project. If None, the name will be the same as the base
name of the project file.
dependencies: List of other Project objects this project is dependent
upon, if not None.
guid: GUID to use for project, if not None.
spec: Dictionary specifying how to build this project.
build_file: Filename of the .gyp file that the vcproj file comes from.
config_platform_overrides: optional dict of configuration platforms to
used in place of the default for this target.
fixpath_prefix: the path used to adjust the behavior of _fixpath
"""
Args:
path: Absolute path to the project file.
name: Name of project. If None, the name will be the same as the base
name of the project file.
dependencies: List of other Project objects this project is dependent
upon, if not None.
guid: GUID to use for project, if not None.
spec: Dictionary specifying how to build this project.
build_file: Filename of the .gyp file that the vcproj file comes from.
config_platform_overrides: optional dict of configuration platforms to
used in place of the default for this target.
fixpath_prefix: the path used to adjust the behavior of _fixpath
"""
self.path = path
self.guid = guid
self.spec = spec
@ -195,16 +195,16 @@ class MSVSSolution:
):
"""Initializes the solution.
Args:
path: Path to solution file.
version: Format version to emit.
entries: List of entries in solution. May contain Folder or Project
objects. May be None, if the folder is empty.
variants: List of build variant strings. If none, a default list will
be used.
websiteProperties: Flag to decide if the website properties section
is generated.
"""
Args:
path: Path to solution file.
version: Format version to emit.
entries: List of entries in solution. May contain Folder or Project
objects. May be None, if the folder is empty.
variants: List of build variant strings. If none, a default list will
be used.
websiteProperties: Flag to decide if the website properties section
is generated.
"""
self.path = path
self.websiteProperties = websiteProperties
self.version = version
@ -230,9 +230,9 @@ class MSVSSolution:
def Write(self, writer=gyp.common.WriteOnDiff):
"""Writes the solution file to disk.
Raises:
IndexError: An entry appears multiple times.
"""
Raises:
IndexError: An entry appears multiple times.
"""
# Walk the entry tree and collect all the folders and projects.
all_entries = set()
entries_to_check = self.entries[:]
@ -285,19 +285,17 @@ class MSVSSolution:
"\tEndProjectSection\r\n"
)
if isinstance(e, MSVSFolder):
if e.items:
f.write("\tProjectSection(SolutionItems) = preProject\r\n")
for i in e.items:
f.write(f"\t\t{i} = {i}\r\n")
f.write("\tEndProjectSection\r\n")
if isinstance(e, MSVSFolder) and e.items:
f.write("\tProjectSection(SolutionItems) = preProject\r\n")
for i in e.items:
f.write(f"\t\t{i} = {i}\r\n")
f.write("\tEndProjectSection\r\n")
if isinstance(e, MSVSProject):
if e.dependencies:
f.write("\tProjectSection(ProjectDependencies) = postProject\r\n")
for d in e.dependencies:
f.write(f"\t\t{d.get_guid()} = {d.get_guid()}\r\n")
f.write("\tEndProjectSection\r\n")
if isinstance(e, MSVSProject) and e.dependencies:
f.write("\tProjectSection(ProjectDependencies) = postProject\r\n")
for d in e.dependencies:
f.write(f"\t\t{d.get_guid()} = {d.get_guid()}\r\n")
f.write("\tEndProjectSection\r\n")
f.write("EndProject\r\n")
@ -353,7 +351,7 @@ class MSVSSolution:
# Folder mappings
# Omit this section if there are no folders
if any([e.entries for e in all_entries if isinstance(e, MSVSFolder)]):
if any(e.entries for e in all_entries if isinstance(e, MSVSFolder)):
f.write("\tGlobalSection(NestedProjects) = preSolution\r\n")
for e in all_entries:
if not isinstance(e, MSVSFolder):

View file

@ -4,7 +4,7 @@
"""Visual Studio project reader/writer."""
import gyp.easy_xml as easy_xml
from gyp import easy_xml
# ------------------------------------------------------------------------------
@ -15,19 +15,19 @@ class Tool:
def __init__(self, name, attrs=None):
"""Initializes the tool.
Args:
name: Tool name.
attrs: Dict of tool attributes; may be None.
"""
Args:
name: Tool name.
attrs: Dict of tool attributes; may be None.
"""
self._attrs = attrs or {}
self._attrs["Name"] = name
def _GetSpecification(self):
"""Creates an element for the tool.
Returns:
A new xml.dom.Element for the tool.
"""
Returns:
A new xml.dom.Element for the tool.
"""
return ["Tool", self._attrs]
@ -37,10 +37,10 @@ class Filter:
def __init__(self, name, contents=None):
"""Initializes the folder.
Args:
name: Filter (folder) name.
contents: List of filenames and/or Filter objects contained.
"""
Args:
name: Filter (folder) name.
contents: List of filenames and/or Filter objects contained.
"""
self.name = name
self.contents = list(contents or [])
@ -54,13 +54,13 @@ class Writer:
def __init__(self, project_path, version, name, guid=None, platforms=None):
"""Initializes the project.
Args:
project_path: Path to the project file.
version: Format version to emit.
name: Name of the project.
guid: GUID to use for project, if not None.
platforms: Array of string, the supported platforms. If null, ['Win32']
"""
Args:
project_path: Path to the project file.
version: Format version to emit.
name: Name of the project.
guid: GUID to use for project, if not None.
platforms: Array of string, the supported platforms. If null, ['Win32']
"""
self.project_path = project_path
self.version = version
self.name = name
@ -79,26 +79,26 @@ class Writer:
self.files_section = ["Files"]
# Keep a dict keyed on filename to speed up access.
self.files_dict = dict()
self.files_dict = {}
def AddToolFile(self, path):
"""Adds a tool file to the project.
Args:
path: Relative path from project to tool file.
"""
Args:
path: Relative path from project to tool file.
"""
self.tool_files_section.append(["ToolFile", {"RelativePath": path}])
def _GetSpecForConfiguration(self, config_type, config_name, attrs, tools):
"""Returns the specification for a configuration.
Args:
config_type: Type of configuration node.
config_name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Returns:
"""
Args:
config_type: Type of configuration node.
config_name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Returns:
"""
# Handle defaults
if not attrs:
attrs = {}
@ -122,23 +122,23 @@ class Writer:
def AddConfig(self, name, attrs=None, tools=None):
"""Adds a configuration to the project.
Args:
name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
"""
Args:
name: Configuration name.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
"""
spec = self._GetSpecForConfiguration("Configuration", name, attrs, tools)
self.configurations_section.append(spec)
def _AddFilesToNode(self, parent, files):
"""Adds files and/or filters to the parent node.
Args:
parent: Destination node
files: A list of Filter objects and/or relative paths to files.
Args:
parent: Destination node
files: A list of Filter objects and/or relative paths to files.
Will call itself recursively, if the files list contains Filter objects.
"""
Will call itself recursively, if the files list contains Filter objects.
"""
for f in files:
if isinstance(f, Filter):
node = ["Filter", {"Name": f.name}]
@ -151,13 +151,13 @@ class Writer:
def AddFiles(self, files):
"""Adds files to the project.
Args:
files: A list of Filter objects and/or relative paths to files.
Args:
files: A list of Filter objects and/or relative paths to files.
This makes a copy of the file/filter tree at the time of this call. If you
later add files to a Filter object which was passed into a previous call
to AddFiles(), it will not be reflected in this project.
"""
This makes a copy of the file/filter tree at the time of this call. If you
later add files to a Filter object which was passed into a previous call
to AddFiles(), it will not be reflected in this project.
"""
self._AddFilesToNode(self.files_section, files)
# TODO(rspangler) This also doesn't handle adding files to an existing
# filter. That is, it doesn't merge the trees.
@ -165,15 +165,15 @@ class Writer:
def AddFileConfig(self, path, config, attrs=None, tools=None):
"""Adds a configuration to a file.
Args:
path: Relative path to the file.
config: Name of configuration to add.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Args:
path: Relative path to the file.
config: Name of configuration to add.
attrs: Dict of configuration attributes; may be None.
tools: List of tools (strings or Tool objects); may be None.
Raises:
ValueError: Relative path does not match any file added via AddFiles().
"""
Raises:
ValueError: Relative path does not match any file added via AddFiles().
"""
# Find the file node with the right relative path
parent = self.files_dict.get(path)
if not parent:

View file

@ -35,10 +35,10 @@ _msbuild_name_of_tool = {}
class _Tool:
"""Represents a tool used by MSVS or MSBuild.
Attributes:
msvs_name: The name of the tool in MSVS.
msbuild_name: The name of the tool in MSBuild.
"""
Attributes:
msvs_name: The name of the tool in MSVS.
msbuild_name: The name of the tool in MSBuild.
"""
def __init__(self, msvs_name, msbuild_name):
self.msvs_name = msvs_name
@ -48,11 +48,11 @@ class _Tool:
def _AddTool(tool):
"""Adds a tool to the four dictionaries used to process settings.
This only defines the tool. Each setting also needs to be added.
This only defines the tool. Each setting also needs to be added.
Args:
tool: The _Tool object to be added.
"""
Args:
tool: The _Tool object to be added.
"""
_msvs_validators[tool.msvs_name] = {}
_msbuild_validators[tool.msbuild_name] = {}
_msvs_to_msbuild_converters[tool.msvs_name] = {}
@ -70,35 +70,35 @@ class _Type:
def ValidateMSVS(self, value):
"""Verifies that the value is legal for MSVS.
Args:
value: the value to check for this type.
Args:
value: the value to check for this type.
Raises:
ValueError if value is not valid for MSVS.
"""
Raises:
ValueError if value is not valid for MSVS.
"""
def ValidateMSBuild(self, value):
"""Verifies that the value is legal for MSBuild.
Args:
value: the value to check for this type.
Args:
value: the value to check for this type.
Raises:
ValueError if value is not valid for MSBuild.
"""
Raises:
ValueError if value is not valid for MSBuild.
"""
def ConvertToMSBuild(self, value):
"""Returns the MSBuild equivalent of the MSVS value given.
Args:
value: the MSVS value to convert.
Args:
value: the MSVS value to convert.
Returns:
the MSBuild equivalent.
Returns:
the MSBuild equivalent.
Raises:
ValueError if value is not valid.
"""
Raises:
ValueError if value is not valid.
"""
return value
@ -141,7 +141,7 @@ class _Boolean(_Type):
"""Boolean settings, can have the values 'false' or 'true'."""
def _Validate(self, value):
if value != "true" and value != "false":
if value not in {"true", "false"}:
raise ValueError("expected bool; got %r" % value)
def ValidateMSVS(self, value):
@ -171,22 +171,22 @@ class _Integer(_Type):
int(value, self._msbuild_base)
def ConvertToMSBuild(self, value):
msbuild_format = (self._msbuild_base == 10) and "%d" or "0x%04x"
msbuild_format = ((self._msbuild_base == 10) and "%d") or "0x%04x"
return msbuild_format % int(value)
class _Enumeration(_Type):
"""Type of settings that is an enumeration.
In MSVS, the values are indexes like '0', '1', and '2'.
MSBuild uses text labels that are more representative, like 'Win32'.
In MSVS, the values are indexes like '0', '1', and '2'.
MSBuild uses text labels that are more representative, like 'Win32'.
Constructor args:
label_list: an array of MSBuild labels that correspond to the MSVS index.
In the rare cases where MSVS has skipped an index value, None is
used in the array to indicate the unused spot.
new: an array of labels that are new to MSBuild.
"""
Constructor args:
label_list: an array of MSBuild labels that correspond to the MSVS index.
In the rare cases where MSVS has skipped an index value, None is
used in the array to indicate the unused spot.
new: an array of labels that are new to MSBuild.
"""
def __init__(self, label_list, new=None):
_Type.__init__(self)
@ -234,23 +234,23 @@ _newly_boolean = _Enumeration(["", "false", "true"])
def _Same(tool, name, setting_type):
"""Defines a setting that has the same name in MSVS and MSBuild.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
_Renamed(tool, name, name, setting_type)
def _Renamed(tool, msvs_name, msbuild_name, setting_type):
"""Defines a setting for which the name has changed.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting.
msbuild_name: the name of the MSBuild setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting.
msbuild_name: the name of the MSBuild setting.
setting_type: the type of this setting.
"""
def _Translate(value, msbuild_settings):
msbuild_tool_settings = _GetMSBuildToolSettings(msbuild_settings, tool)
@ -272,13 +272,13 @@ def _MovedAndRenamed(
):
"""Defines a setting that may have moved to a new section.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_settings_name: the MSVS name of the setting.
msbuild_tool_name: the name of the MSBuild tool to place the setting under.
msbuild_settings_name: the MSBuild name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_settings_name: the MSVS name of the setting.
msbuild_tool_name: the name of the MSBuild tool to place the setting under.
msbuild_settings_name: the MSBuild name of the setting.
setting_type: the type of this setting.
"""
def _Translate(value, msbuild_settings):
tool_settings = msbuild_settings.setdefault(msbuild_tool_name, {})
@ -293,11 +293,11 @@ def _MovedAndRenamed(
def _MSVSOnly(tool, name, setting_type):
"""Defines a setting that is only found in MSVS.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
def _Translate(unused_value, unused_msbuild_settings):
# Since this is for MSVS only settings, no translation will happen.
@ -310,11 +310,11 @@ def _MSVSOnly(tool, name, setting_type):
def _MSBuildOnly(tool, name, setting_type):
"""Defines a setting that is only found in MSBuild.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
name: the name of the setting.
setting_type: the type of this setting.
"""
def _Translate(value, msbuild_settings):
# Let msbuild-only properties get translated as-is from msvs_settings.
@ -328,11 +328,11 @@ def _MSBuildOnly(tool, name, setting_type):
def _ConvertedToAdditionalOption(tool, msvs_name, flag):
"""Defines a setting that's handled via a command line option in MSBuild.
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting that if 'true' becomes a flag
flag: the flag to insert at the end of the AdditionalOptions
"""
Args:
tool: a dictionary that gives the names of the tool for MSVS and MSBuild.
msvs_name: the name of the MSVS setting that if 'true' becomes a flag
flag: the flag to insert at the end of the AdditionalOptions
"""
def _Translate(value, msbuild_settings):
if value == "true":
@ -384,20 +384,19 @@ _EXCLUDED_SUFFIX_RE = re.compile("^(.*)_excluded$")
def _ValidateExclusionSetting(setting, settings, error_msg, stderr=sys.stderr):
"""Verify that 'setting' is valid if it is generated from an exclusion list.
If the setting appears to be generated from an exclusion list, the root name
is checked.
If the setting appears to be generated from an exclusion list, the root name
is checked.
Args:
setting: A string that is the setting name to validate
settings: A dictionary where the keys are valid settings
error_msg: The message to emit in the event of error
stderr: The stream receiving the error messages.
"""
Args:
setting: A string that is the setting name to validate
settings: A dictionary where the keys are valid settings
error_msg: The message to emit in the event of error
stderr: The stream receiving the error messages.
"""
# This may be unrecognized because it's an exclusion list. If the
# setting name has the _excluded suffix, then check the root name.
unrecognized = True
m = re.match(_EXCLUDED_SUFFIX_RE, setting)
if m:
if m := re.match(_EXCLUDED_SUFFIX_RE, setting):
root_setting = m.group(1)
unrecognized = root_setting not in settings
@ -409,11 +408,11 @@ def _ValidateExclusionSetting(setting, settings, error_msg, stderr=sys.stderr):
def FixVCMacroSlashes(s):
"""Replace macros which have excessive following slashes.
These macros are known to have a built-in trailing slash. Furthermore, many
scripts hiccup on processing paths with extra slashes in the middle.
These macros are known to have a built-in trailing slash. Furthermore, many
scripts hiccup on processing paths with extra slashes in the middle.
This list is probably not exhaustive. Add as needed.
"""
This list is probably not exhaustive. Add as needed.
"""
if "$" in s:
s = fix_vc_macro_slashes_regex.sub(r"\1", s)
return s
@ -422,8 +421,8 @@ def FixVCMacroSlashes(s):
def ConvertVCMacrosToMSBuild(s):
"""Convert the MSVS macros found in the string to the MSBuild equivalent.
This list is probably not exhaustive. Add as needed.
"""
This list is probably not exhaustive. Add as needed.
"""
if "$" in s:
replace_map = {
"$(ConfigurationName)": "$(Configuration)",
@ -445,16 +444,16 @@ def ConvertVCMacrosToMSBuild(s):
def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr):
"""Converts MSVS settings (VS2008 and earlier) to MSBuild settings (VS2010+).
Args:
msvs_settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
Args:
msvs_settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
Returns:
A dictionary of MSBuild settings. The key is either the MSBuild tool name
or the empty string (for the global settings). The values are themselves
dictionaries of settings and their values.
"""
Returns:
A dictionary of MSBuild settings. The key is either the MSBuild tool name
or the empty string (for the global settings). The values are themselves
dictionaries of settings and their values.
"""
msbuild_settings = {}
for msvs_tool_name, msvs_tool_settings in msvs_settings.items():
if msvs_tool_name in _msvs_to_msbuild_converters:
@ -493,36 +492,36 @@ def ConvertToMSBuildSettings(msvs_settings, stderr=sys.stderr):
def ValidateMSVSSettings(settings, stderr=sys.stderr):
"""Validates that the names of the settings are valid for MSVS.
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
_ValidateSettings(_msvs_validators, settings, stderr)
def ValidateMSBuildSettings(settings, stderr=sys.stderr):
"""Validates that the names of the settings are valid for MSBuild.
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
Args:
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
_ValidateSettings(_msbuild_validators, settings, stderr)
def _ValidateSettings(validators, settings, stderr):
"""Validates that the settings are valid for MSBuild or MSVS.
We currently only validate the names of the settings, not their values.
We currently only validate the names of the settings, not their values.
Args:
validators: A dictionary of tools and their validators.
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
Args:
validators: A dictionary of tools and their validators.
settings: A dictionary. The key is the tool name. The values are
themselves dictionaries of settings and their values.
stderr: The stream receiving the error messages.
"""
for tool_name in settings:
if tool_name in validators:
tool_validators = validators[tool_name]
@ -638,7 +637,9 @@ _Same(
),
) # /RTC1
_Same(
_compile, "BrowseInformation", _Enumeration(["false", "true", "true"]) # /FR
_compile,
"BrowseInformation",
_Enumeration(["false", "true", "true"]), # /FR
) # /Fr
_Same(
_compile,
@ -696,7 +697,9 @@ _Same(
_Enumeration(["false", "Sync", "Async"], new=["SyncCThrow"]), # /EHsc # /EHa
) # /EHs
_Same(
_compile, "FavorSizeOrSpeed", _Enumeration(["Neither", "Speed", "Size"]) # /Ot
_compile,
"FavorSizeOrSpeed",
_Enumeration(["Neither", "Speed", "Size"]), # /Ot
) # /Os
_Same(
_compile,
@ -793,6 +796,8 @@ _MSBuildOnly(
_compile, "CompileAsManaged", _Enumeration([], new=["false", "true"])
) # /clr
_MSBuildOnly(_compile, "CreateHotpatchableImage", _boolean) # /hotpatch
_MSBuildOnly(_compile, "LanguageStandard", _string)
_MSBuildOnly(_compile, "LanguageStandard_C", _string)
_MSBuildOnly(_compile, "MultiProcessorCompilation", _boolean) # /MP
_MSBuildOnly(_compile, "PreprocessOutputPath", _string) # /Fi
_MSBuildOnly(_compile, "ProcessorNumber", _integer) # the number of processors
@ -907,7 +912,9 @@ _target_machine_enumeration = _Enumeration(
) # /MACHINE:X64
_Same(
_link, "AssemblyDebug", _Enumeration(["", "true", "false"]) # /ASSEMBLYDEBUG
_link,
"AssemblyDebug",
_Enumeration(["", "true", "false"]), # /ASSEMBLYDEBUG
) # /ASSEMBLYDEBUG:DISABLE
_Same(
_link,
@ -1157,17 +1164,23 @@ _Renamed(_midl, "ValidateParameters", "ValidateAllParameters", _boolean) # /rob
_MSBuildOnly(_midl, "ApplicationConfigurationMode", _boolean) # /app_config
_MSBuildOnly(_midl, "ClientStubFile", _file_name) # /cstub
_MSBuildOnly(
_midl, "GenerateClientFiles", _Enumeration([], new=["Stub", "None"]) # /client stub
_midl,
"GenerateClientFiles",
_Enumeration([], new=["Stub", "None"]), # /client stub
) # /client none
_MSBuildOnly(
_midl, "GenerateServerFiles", _Enumeration([], new=["Stub", "None"]) # /client stub
_midl,
"GenerateServerFiles",
_Enumeration([], new=["Stub", "None"]), # /client stub
) # /client none
_MSBuildOnly(_midl, "LocaleID", _integer) # /lcid DECIMAL
_MSBuildOnly(_midl, "ServerStubFile", _file_name) # /sstub
_MSBuildOnly(_midl, "SuppressCompilerWarnings", _boolean) # /no_warn
_MSBuildOnly(_midl, "TrackerLogDirectory", _folder_name)
_MSBuildOnly(
_midl, "TypeLibFormat", _Enumeration([], new=["NewFormat", "OldFormat"]) # /newtlb
_midl,
"TypeLibFormat",
_Enumeration([], new=["NewFormat", "OldFormat"]), # /newtlb
) # /oldtlb

View file

@ -7,10 +7,10 @@
"""Unit tests for the MSVSSettings.py file."""
import unittest
import gyp.MSVSSettings as MSVSSettings
from io import StringIO
from gyp import MSVSSettings
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
@ -1143,47 +1143,47 @@ class TestSequenceFunctions(unittest.TestCase):
def testConvertToMSBuildSettings_actual(self):
"""Tests the conversion of an actual project.
A VS2008 project with most of the options defined was created through the
VS2008 IDE. It was then converted to VS2010. The tool settings found in
the .vcproj and .vcxproj files were converted to the two dictionaries
msvs_settings and expected_msbuild_settings.
A VS2008 project with most of the options defined was created through the
VS2008 IDE. It was then converted to VS2010. The tool settings found in
the .vcproj and .vcxproj files were converted to the two dictionaries
msvs_settings and expected_msbuild_settings.
Note that for many settings, the VS2010 converter adds macros like
%(AdditionalIncludeDirectories) to make sure than inherited values are
included. Since the Gyp projects we generate do not use inheritance,
we removed these macros. They were:
ClCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)'
AdditionalOptions: ' %(AdditionalOptions)'
AdditionalUsingDirectories: ';%(AdditionalUsingDirectories)'
DisableSpecificWarnings: ';%(DisableSpecificWarnings)',
ForcedIncludeFiles: ';%(ForcedIncludeFiles)',
ForcedUsingFiles: ';%(ForcedUsingFiles)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
UndefinePreprocessorDefinitions:
';%(UndefinePreprocessorDefinitions)',
Link:
AdditionalDependencies: ';%(AdditionalDependencies)',
AdditionalLibraryDirectories: ';%(AdditionalLibraryDirectories)',
AdditionalManifestDependencies:
';%(AdditionalManifestDependencies)',
AdditionalOptions: ' %(AdditionalOptions)',
AddModuleNamesToAssembly: ';%(AddModuleNamesToAssembly)',
AssemblyLinkResource: ';%(AssemblyLinkResource)',
DelayLoadDLLs: ';%(DelayLoadDLLs)',
EmbedManagedResourceFile: ';%(EmbedManagedResourceFile)',
ForceSymbolReferences: ';%(ForceSymbolReferences)',
IgnoreSpecificDefaultLibraries:
';%(IgnoreSpecificDefaultLibraries)',
ResourceCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)',
AdditionalOptions: ' %(AdditionalOptions)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
Manifest:
AdditionalManifestFiles: ';%(AdditionalManifestFiles)',
AdditionalOptions: ' %(AdditionalOptions)',
InputResourceManifests: ';%(InputResourceManifests)',
"""
Note that for many settings, the VS2010 converter adds macros like
%(AdditionalIncludeDirectories) to make sure than inherited values are
included. Since the Gyp projects we generate do not use inheritance,
we removed these macros. They were:
ClCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)'
AdditionalOptions: ' %(AdditionalOptions)'
AdditionalUsingDirectories: ';%(AdditionalUsingDirectories)'
DisableSpecificWarnings: ';%(DisableSpecificWarnings)',
ForcedIncludeFiles: ';%(ForcedIncludeFiles)',
ForcedUsingFiles: ';%(ForcedUsingFiles)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
UndefinePreprocessorDefinitions:
';%(UndefinePreprocessorDefinitions)',
Link:
AdditionalDependencies: ';%(AdditionalDependencies)',
AdditionalLibraryDirectories: ';%(AdditionalLibraryDirectories)',
AdditionalManifestDependencies:
';%(AdditionalManifestDependencies)',
AdditionalOptions: ' %(AdditionalOptions)',
AddModuleNamesToAssembly: ';%(AddModuleNamesToAssembly)',
AssemblyLinkResource: ';%(AssemblyLinkResource)',
DelayLoadDLLs: ';%(DelayLoadDLLs)',
EmbedManagedResourceFile: ';%(EmbedManagedResourceFile)',
ForceSymbolReferences: ';%(ForceSymbolReferences)',
IgnoreSpecificDefaultLibraries:
';%(IgnoreSpecificDefaultLibraries)',
ResourceCompile:
AdditionalIncludeDirectories: ';%(AdditionalIncludeDirectories)',
AdditionalOptions: ' %(AdditionalOptions)',
PreprocessorDefinitions: ';%(PreprocessorDefinitions)',
Manifest:
AdditionalManifestFiles: ';%(AdditionalManifestFiles)',
AdditionalOptions: ' %(AdditionalOptions)',
InputResourceManifests: ';%(InputResourceManifests)',
"""
msvs_settings = {
"VCCLCompilerTool": {
"AdditionalIncludeDirectories": "dir1",
@ -1346,8 +1346,7 @@ class TestSequenceFunctions(unittest.TestCase):
"EmbedManifest": "false",
"GenerateCatalogFiles": "true",
"InputResourceManifests": "asfsfdafs",
"ManifestResourceFile":
"$(IntDir)\\$(TargetFileName).embed.manifest.resfdsf",
"ManifestResourceFile": "$(IntDir)\\$(TargetFileName).embed.manifest.resfdsf", # noqa: E501
"OutputManifestFile": "$(TargetPath).manifestdfs",
"RegistrarScriptFile": "sdfsfd",
"ReplacementsFile": "sdffsd",
@ -1531,8 +1530,7 @@ class TestSequenceFunctions(unittest.TestCase):
"LinkIncremental": "",
},
"ManifestResourceCompile": {
"ResourceOutputFileName":
"$(IntDir)$(TargetFileName).embed.manifest.resfdsf"
"ResourceOutputFileName": "$(IntDir)$(TargetFileName).embed.manifest.resfdsf" # noqa: E501
},
}
self.maxDiff = 9999 # on failure display a long diff

View file

@ -4,7 +4,7 @@
"""Visual Studio project reader/writer."""
import gyp.easy_xml as easy_xml
from gyp import easy_xml
class Writer:
@ -13,10 +13,10 @@ class Writer:
def __init__(self, tool_file_path, name):
"""Initializes the tool file.
Args:
tool_file_path: Path to the tool file.
name: Name of the tool file.
"""
Args:
tool_file_path: Path to the tool file.
name: Name of the tool file.
"""
self.tool_file_path = tool_file_path
self.name = name
self.rules_section = ["Rules"]
@ -26,14 +26,14 @@ class Writer:
):
"""Adds a rule to the tool file.
Args:
name: Name of the rule.
description: Description of the rule.
cmd: Command line of the rule.
additional_dependencies: other files which may trigger the rule.
outputs: outputs of the rule.
extensions: extensions handled by the rule.
"""
Args:
name: Name of the rule.
description: Description of the rule.
cmd: Command line of the rule.
additional_dependencies: other files which may trigger the rule.
outputs: outputs of the rule.
extensions: extensions handled by the rule.
"""
rule = [
"CustomBuildRule",
{

View file

@ -8,19 +8,18 @@ import os
import re
import socket # for gethostname
import gyp.easy_xml as easy_xml
from gyp import easy_xml
# ------------------------------------------------------------------------------
def _FindCommandInPath(command):
"""If there are no slashes in the command given, this function
searches the PATH env to find the given command, and converts it
to an absolute path. We have to do this because MSVS is looking
for an actual file to launch a debugger on, not just a command
line. Note that this happens at GYP time, so anything needing to
be built needs to have a full path."""
searches the PATH env to find the given command, and converts it
to an absolute path. We have to do this because MSVS is looking
for an actual file to launch a debugger on, not just a command
line. Note that this happens at GYP time, so anything needing to
be built needs to have a full path."""
if "/" in command or "\\" in command:
# If the command already has path elements (either relative or
# absolute), then assume it is constructed properly.
@ -59,11 +58,11 @@ class Writer:
def __init__(self, user_file_path, version, name):
"""Initializes the user file.
Args:
user_file_path: Path to the user file.
version: Version info.
name: Name of the user file.
"""
Args:
user_file_path: Path to the user file.
version: Version info.
name: Name of the user file.
"""
self.user_file_path = user_file_path
self.version = version
self.name = name
@ -72,9 +71,9 @@ class Writer:
def AddConfig(self, name):
"""Adds a configuration to the project.
Args:
name: Configuration name.
"""
Args:
name: Configuration name.
"""
self.configurations[name] = ["Configuration", {"Name": name}]
def AddDebugSettings(
@ -82,12 +81,12 @@ class Writer:
):
"""Adds a DebugSettings node to the user file for a particular config.
Args:
command: command line to run. First element in the list is the
executable. All elements of the command will be quoted if
necessary.
working_directory: other files which may trigger the rule. (optional)
"""
Args:
command: command line to run. First element in the list is the
executable. All elements of the command will be quoted if
necessary.
working_directory: other files which may trigger the rule. (optional)
"""
command = _QuoteWin32CommandLineArgs(command)
abs_command = _FindCommandInPath(command[0])

View file

@ -7,7 +7,6 @@
import copy
import os
# A dictionary mapping supported target types to extensions.
TARGET_TYPE_EXT = {
"executable": "exe",
@ -30,13 +29,13 @@ def _GetLargePdbShimCcPath():
def _DeepCopySomeKeys(in_dict, keys):
"""Performs a partial deep-copy on |in_dict|, only copying the keys in |keys|.
Arguments:
in_dict: The dictionary to copy.
keys: The keys to be copied. If a key is in this list and doesn't exist in
|in_dict| this is not an error.
Returns:
The partially deep-copied dictionary.
"""
Arguments:
in_dict: The dictionary to copy.
keys: The keys to be copied. If a key is in this list and doesn't exist in
|in_dict| this is not an error.
Returns:
The partially deep-copied dictionary.
"""
d = {}
for key in keys:
if key not in in_dict:
@ -48,12 +47,12 @@ def _DeepCopySomeKeys(in_dict, keys):
def _SuffixName(name, suffix):
"""Add a suffix to the end of a target.
Arguments:
name: name of the target (foo#target)
suffix: the suffix to be added
Returns:
Target name with suffix added (foo_suffix#target)
"""
Arguments:
name: name of the target (foo#target)
suffix: the suffix to be added
Returns:
Target name with suffix added (foo_suffix#target)
"""
parts = name.rsplit("#", 1)
parts[0] = f"{parts[0]}_{suffix}"
return "#".join(parts)
@ -62,24 +61,24 @@ def _SuffixName(name, suffix):
def _ShardName(name, number):
"""Add a shard number to the end of a target.
Arguments:
name: name of the target (foo#target)
number: shard number
Returns:
Target name with shard added (foo_1#target)
"""
Arguments:
name: name of the target (foo#target)
number: shard number
Returns:
Target name with shard added (foo_1#target)
"""
return _SuffixName(name, str(number))
def ShardTargets(target_list, target_dicts):
"""Shard some targets apart to work around the linkers limits.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
Returns:
Tuple of the new sharded versions of the inputs.
"""
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
Returns:
Tuple of the new sharded versions of the inputs.
"""
# Gather the targets to shard, and how many pieces.
targets_to_shard = {}
for t in target_dicts:
@ -129,22 +128,22 @@ def ShardTargets(target_list, target_dicts):
def _GetPdbPath(target_dict, config_name, vars):
"""Returns the path to the PDB file that will be generated by a given
configuration.
configuration.
The lookup proceeds as follows:
- Look for an explicit path in the VCLinkerTool configuration block.
- Look for an 'msvs_large_pdb_path' variable.
- Use '<(PRODUCT_DIR)/<(product_name).(exe|dll).pdb' if 'product_name' is
specified.
- Use '<(PRODUCT_DIR)/<(target_name).(exe|dll).pdb'.
The lookup proceeds as follows:
- Look for an explicit path in the VCLinkerTool configuration block.
- Look for an 'msvs_large_pdb_path' variable.
- Use '<(PRODUCT_DIR)/<(product_name).(exe|dll).pdb' if 'product_name' is
specified.
- Use '<(PRODUCT_DIR)/<(target_name).(exe|dll).pdb'.
Arguments:
target_dict: The target dictionary to be searched.
config_name: The name of the configuration of interest.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
The path of the corresponding PDB file.
"""
Arguments:
target_dict: The target dictionary to be searched.
config_name: The name of the configuration of interest.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
The path of the corresponding PDB file.
"""
config = target_dict["configurations"][config_name]
msvs = config.setdefault("msvs_settings", {})
@ -169,16 +168,16 @@ def _GetPdbPath(target_dict, config_name, vars):
def InsertLargePdbShims(target_list, target_dicts, vars):
"""Insert a shim target that forces the linker to use 4KB pagesize PDBs.
This is a workaround for targets with PDBs greater than 1GB in size, the
limit for the 1KB pagesize PDBs created by the linker by default.
This is a workaround for targets with PDBs greater than 1GB in size, the
limit for the 1KB pagesize PDBs created by the linker by default.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
Tuple of the shimmed version of the inputs.
"""
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
vars: A dictionary of common GYP variables with generator-specific values.
Returns:
Tuple of the shimmed version of the inputs.
"""
# Determine which targets need shimming.
targets_to_shim = []
for t in target_dicts:

View file

@ -5,11 +5,11 @@
"""Handle version information related to Visual Stuio."""
import errno
import glob
import os
import re
import subprocess
import sys
import glob
def JoinPath(*args):
@ -69,25 +69,25 @@ class VisualStudioVersion:
def ProjectExtension(self):
"""Returns the file extension for the project."""
return self.uses_vcxproj and ".vcxproj" or ".vcproj"
return (self.uses_vcxproj and ".vcxproj") or ".vcproj"
def Path(self):
"""Returns the path to Visual Studio installation."""
return self.path
def ToolPath(self, tool):
"""Returns the path to a given compiler tool. """
"""Returns the path to a given compiler tool."""
return os.path.normpath(os.path.join(self.path, "VC/bin", tool))
def DefaultToolset(self):
"""Returns the msbuild toolset version that will be used in the absence
of a user override."""
of a user override."""
return self.default_toolset
def _SetupScriptInternal(self, target_arch):
"""Returns a command (with arguments) to be used to set up the
environment."""
assert target_arch in ("x86", "x64"), "target_arch not supported"
environment."""
assert target_arch in ("x86", "x64", "arm64"), "target_arch not supported"
# If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the
# depot_tools build tools and should run SetEnv.Cmd to set up the
# environment. The check for WindowsSDKDir alone is not sufficient because
@ -109,8 +109,16 @@ class VisualStudioVersion:
)
# Always use a native executable, cross-compiling if necessary.
host_arch = "amd64" if is_host_arch_x64 else "x86"
msvc_target_arch = "amd64" if target_arch == "x64" else "x86"
host_arch = (
"amd64"
if is_host_arch_x64
else (
"arm64"
if os.environ.get("PROCESSOR_ARCHITECTURE") == "ARM64"
else "x86"
)
)
msvc_target_arch = {"x64": "amd64"}.get(target_arch, target_arch)
arg = host_arch
if host_arch != msvc_target_arch:
arg += "_" + msvc_target_arch
@ -154,16 +162,16 @@ class VisualStudioVersion:
def _RegistryQueryBase(sysdir, key, value):
"""Use reg.exe to read a particular key.
While ideally we might use the win32 module, we would like gyp to be
python neutral, so for instance cygwin python lacks this module.
While ideally we might use the win32 module, we would like gyp to be
python neutral, so for instance cygwin python lacks this module.
Arguments:
sysdir: The system subdirectory to attempt to launch reg.exe from.
key: The registry key to read from.
value: The particular value to read.
Return:
stdout from reg.exe, or None for failure.
"""
Arguments:
sysdir: The system subdirectory to attempt to launch reg.exe from.
key: The registry key to read from.
value: The particular value to read.
Return:
stdout from reg.exe, or None for failure.
"""
# Skip if not on Windows or Python Win32 setup issue
if sys.platform not in ("win32", "cygwin"):
return None
@ -184,20 +192,20 @@ def _RegistryQueryBase(sysdir, key, value):
def _RegistryQuery(key, value=None):
r"""Use reg.exe to read a particular key through _RegistryQueryBase.
First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
that fails, it falls back to System32. Sysnative is available on Vista and
up and available on Windows Server 2003 and XP through KB patch 942589. Note
that Sysnative will always fail if using 64-bit python due to it being a
virtual directory and System32 will work correctly in the first place.
First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
that fails, it falls back to System32. Sysnative is available on Vista and
up and available on Windows Server 2003 and XP through KB patch 942589. Note
that Sysnative will always fail if using 64-bit python due to it being a
virtual directory and System32 will work correctly in the first place.
KB 942589 - http://support.microsoft.com/kb/942589/en-us.
KB 942589 - http://support.microsoft.com/kb/942589/en-us.
Arguments:
key: The registry key.
value: The particular registry value to read (optional).
Return:
stdout from reg.exe, or None for failure.
"""
Arguments:
key: The registry key.
value: The particular registry value to read (optional).
Return:
stdout from reg.exe, or None for failure.
"""
text = None
try:
text = _RegistryQueryBase("Sysnative", key, value)
@ -212,14 +220,15 @@ def _RegistryQuery(key, value=None):
def _RegistryGetValueUsingWinReg(key, value):
"""Use the _winreg module to obtain the value of a registry key.
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure. Throws
ImportError if winreg is unavailable.
"""
from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure. Throws
ImportError if winreg is unavailable.
"""
from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx # noqa: PLC0415
try:
root, subkey = key.split("\\", 1)
assert root == "HKLM" # Only need HKLM for now.
@ -232,17 +241,17 @@ def _RegistryGetValueUsingWinReg(key, value):
def _RegistryGetValue(key, value):
"""Use _winreg or reg.exe to obtain the value of a registry key.
Using _winreg is preferable because it solves an issue on some corporate
environments where access to reg.exe is locked down. However, we still need
to fallback to reg.exe for the case where the _winreg module is not available
(for example in cygwin python).
Using _winreg is preferable because it solves an issue on some corporate
environments where access to reg.exe is locked down. However, we still need
to fallback to reg.exe for the case where the _winreg module is not available
(for example in cygwin python).
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure.
"""
Args:
key: The registry key.
value: The particular registry value to read.
Return:
contents of the registry key's value, or None on failure.
"""
try:
return _RegistryGetValueUsingWinReg(key, value)
except ImportError:
@ -262,13 +271,25 @@ def _RegistryGetValue(key, value):
def _CreateVersion(name, path, sdk_based=False):
"""Sets up MSVS project generation.
Setup is based off the GYP_MSVS_VERSION environment variable or whatever is
autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is
passed in that doesn't match a value in versions python will throw a error.
"""
Setup is based off the GYP_MSVS_VERSION environment variable or whatever is
autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is
passed in that doesn't match a value in versions python will throw a error.
"""
if path:
path = os.path.normpath(path)
versions = {
"2026": VisualStudioVersion(
"2026",
"Visual Studio 2026",
solution_version="12.00",
project_version="18.0",
flat_sln=False,
uses_vcxproj=True,
path=path,
sdk_based=sdk_based,
default_toolset="v145",
compatible_sdks=["v8.1", "v10.0"],
),
"2022": VisualStudioVersion(
"2022",
"Visual Studio 2022",
@ -435,22 +456,22 @@ def _ConvertToCygpath(path):
def _DetectVisualStudioVersions(versions_to_check, force_express):
"""Collect the list of installed visual studio versions.
Returns:
A list of visual studio versions installed in descending order of
usage preference.
Base this on the registry and a quick check if devenv.exe exists.
Possibilities are:
2005(e) - Visual Studio 2005 (8)
2008(e) - Visual Studio 2008 (9)
2010(e) - Visual Studio 2010 (10)
2012(e) - Visual Studio 2012 (11)
2013(e) - Visual Studio 2013 (12)
2015 - Visual Studio 2015 (14)
2017 - Visual Studio 2017 (15)
2019 - Visual Studio 2019 (16)
2022 - Visual Studio 2022 (17)
Where (e) is e for express editions of MSVS and blank otherwise.
"""
Returns:
A list of visual studio versions installed in descending order of
usage preference.
Base this on the registry and a quick check if devenv.exe exists.
Possibilities are:
2005(e) - Visual Studio 2005 (8)
2008(e) - Visual Studio 2008 (9)
2010(e) - Visual Studio 2010 (10)
2012(e) - Visual Studio 2012 (11)
2013(e) - Visual Studio 2013 (12)
2015 - Visual Studio 2015 (14)
2017 - Visual Studio 2017 (15)
2019 - Visual Studio 2019 (16)
2022 - Visual Studio 2022 (17)
Where (e) is e for express editions of MSVS and blank otherwise.
"""
version_to_year = {
"8.0": "2005",
"9.0": "2008",
@ -461,6 +482,7 @@ def _DetectVisualStudioVersions(versions_to_check, force_express):
"15.0": "2017",
"16.0": "2019",
"17.0": "2022",
"18.0": "2026",
}
versions = []
for version in versions_to_check:
@ -527,16 +549,27 @@ def _DetectVisualStudioVersions(versions_to_check, force_express):
def SelectVisualStudioVersion(version="auto", allow_fallback=True):
"""Select which version of Visual Studio projects to generate.
Arguments:
version: Hook to allow caller to force a particular version (vs auto).
Returns:
An object representing a visual studio project format version.
"""
Arguments:
version: Hook to allow caller to force a particular version (vs auto).
Returns:
An object representing a visual studio project format version.
"""
# In auto mode, check environment variable for override.
if version == "auto":
version = os.environ.get("GYP_MSVS_VERSION", "auto")
version_map = {
"auto": ("17.0", "16.0", "15.0", "14.0", "12.0", "10.0", "9.0", "8.0", "11.0"),
"auto": (
"18.0",
"17.0",
"16.0",
"15.0",
"14.0",
"12.0",
"10.0",
"9.0",
"8.0",
"11.0",
),
"2005": ("8.0",),
"2005e": ("8.0",),
"2008": ("9.0",),
@ -551,9 +584,9 @@ def SelectVisualStudioVersion(version="auto", allow_fallback=True):
"2017": ("15.0",),
"2019": ("16.0",),
"2022": ("17.0",),
"2026": ("18.0",),
}
override_path = os.environ.get("GYP_MSVS_OVERRIDE_PATH")
if override_path:
if override_path := os.environ.get("GYP_MSVS_OVERRIDE_PATH"):
msvs_version = os.environ.get("GYP_MSVS_VERSION")
if not msvs_version:
raise ValueError(

View file

@ -4,17 +4,19 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
import copy
import gyp.input
import argparse
import copy
import os.path
import re
import shlex
import sys
import traceback
from gyp.common import GypError
from importlib.metadata import version
import gyp.input
from gyp.common import GypError
# Default debug modes for GYP
debug = {}
@ -25,6 +27,20 @@ DEBUG_VARIABLES = "variables"
DEBUG_INCLUDES = "includes"
def EscapeForCString(string: bytes | str) -> str:
if isinstance(string, str):
string = string.encode(encoding="utf8")
backslash_or_double_quote = {ord("\\"), ord('"')}
result = ""
for char in string:
if char in backslash_or_double_quote or not 32 <= char < 127:
result += "\\%03o" % char
else:
result += chr(char)
return result
def DebugOutput(mode, message, *args):
if "all" in gyp.debug or mode in gyp.debug:
ctx = ("unknown", 0, "unknown")
@ -63,11 +79,11 @@ def Load(
circular_check=True,
):
"""
Loads one or more specified build files.
default_variables and includes will be copied before use.
Returns the generator for the specified format and the
data returned by loading the specified build files.
"""
Loads one or more specified build files.
default_variables and includes will be copied before use.
Returns the generator for the specified format and the
data returned by loading the specified build files.
"""
if params is None:
params = {}
@ -101,21 +117,24 @@ def Load(
# These parameters are passed in order (as opposed to by key)
# because ActivePython cannot handle key parameters to __import__.
generator = __import__(generator_name, globals(), locals(), generator_name)
for (key, val) in generator.generator_default_variables.items():
for key, val in generator.generator_default_variables.items():
default_variables.setdefault(key, val)
output_dir = params["options"].generator_output or params["options"].toplevel_dir
if default_variables["GENERATOR"] == "ninja":
default_variables.setdefault(
"PRODUCT_DIR_ABS",
os.path.join(output_dir, "out", default_variables["build_type"]),
product_dir_abs = os.path.join(
output_dir, "out", default_variables.get("build_type", "default")
)
else:
default_variables.setdefault(
"PRODUCT_DIR_ABS",
os.path.join(output_dir, default_variables["CONFIGURATION_NAME"]),
product_dir_abs = os.path.join(
output_dir, default_variables["CONFIGURATION_NAME"]
)
default_variables.setdefault("PRODUCT_DIR_ABS", product_dir_abs)
default_variables.setdefault(
"PRODUCT_DIR_ABS_CSTR", EscapeForCString(product_dir_abs)
)
# Give the generator the opportunity to set additional variables based on
# the params it will receive in the output phase.
if getattr(generator, "CalculateVariables", None):
@ -168,10 +187,10 @@ def Load(
def NameValueListToDict(name_value_list):
"""
Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
of the pairs. If a string is simply NAME, then the value in the dictionary
is set to True. If VALUE can be converted to an integer, it is.
"""
Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary
of the pairs. If a string is simply NAME, then the value in the dictionary
is set to True. If VALUE can be converted to an integer, it is.
"""
result = {}
for item in name_value_list:
tokens = item.split("=", 1)
@ -190,8 +209,7 @@ def NameValueListToDict(name_value_list):
def ShlexEnv(env_name):
flags = os.environ.get(env_name, [])
if flags:
if flags := os.environ.get(env_name) or []:
flags = shlex.split(flags)
return flags
@ -205,13 +223,13 @@ def FormatOpt(opt, value):
def RegenerateAppendFlag(flag, values, predicate, env_name, options):
"""Regenerate a list of command line flags, for an option of action='append'.
The |env_name|, if given, is checked in the environment and used to generate
an initial list of options, then the options that were specified on the
command line (given in |values|) are appended. This matches the handling of
environment variables and command line flags where command line flags override
the environment, while not requiring the environment to be set when the flags
are used again.
"""
The |env_name|, if given, is checked in the environment and used to generate
an initial list of options, then the options that were specified on the
command line (given in |values|) are appended. This matches the handling of
environment variables and command line flags where command line flags override
the environment, while not requiring the environment to be set when the flags
are used again.
"""
flags = []
if options.use_environment and env_name:
for flag_value in ShlexEnv(env_name):
@ -227,14 +245,14 @@ def RegenerateAppendFlag(flag, values, predicate, env_name, options):
def RegenerateFlags(options):
"""Given a parsed options object, and taking the environment variables into
account, returns a list of flags that should regenerate an equivalent options
object (even in the absence of the environment variables.)
account, returns a list of flags that should regenerate an equivalent options
object (even in the absence of the environment variables.)
Any path options will be normalized relative to depth.
Any path options will be normalized relative to depth.
The format flag is not included, as it is assumed the calling generator will
set that as appropriate.
"""
The format flag is not included, as it is assumed the calling generator will
set that as appropriate.
"""
def FixPath(path):
path = gyp.common.FixIfRelativePath(path, options.depth)
@ -251,7 +269,7 @@ def RegenerateFlags(options):
for name, metadata in options._regeneration_metadata.items():
opt = metadata["opt"]
value = getattr(options, name)
value_predicate = metadata["type"] == "path" and FixPath or Noop
value_predicate = (metadata["type"] == "path" and FixPath) or Noop
action = metadata["action"]
env_name = metadata["env_name"]
if action == "append":
@ -292,15 +310,15 @@ class RegeneratableOptionParser(argparse.ArgumentParser):
def add_argument(self, *args, **kw):
"""Add an option to the parser.
This accepts the same arguments as ArgumentParser.add_argument, plus the
following:
regenerate: can be set to False to prevent this option from being included
in regeneration.
env_name: name of environment variable that additional values for this
option come from.
type: adds type='path', to tell the regenerator that the values of
this option need to be made relative to options.depth
"""
This accepts the same arguments as ArgumentParser.add_argument, plus the
following:
regenerate: can be set to False to prevent this option from being included
in regeneration.
env_name: name of environment variable that additional values for this
option come from.
type: adds type='path', to tell the regenerator that the values of
this option need to be made relative to options.depth
"""
env_name = kw.pop("env_name", None)
if "dest" in kw and kw.pop("regenerate", True):
dest = kw["dest"]
@ -328,7 +346,7 @@ class RegeneratableOptionParser(argparse.ArgumentParser):
def gyp_main(args):
my_name = os.path.basename(sys.argv[0])
usage = "usage: %(prog)s [options ...] [build_file ...]"
usage = "%(prog)s [options ...] [build_file ...]"
parser = RegeneratableOptionParser(usage=usage.replace("%s", "%(prog)s"))
parser.add_argument(
@ -346,7 +364,7 @@ def gyp_main(args):
action="store",
env_name="GYP_CONFIG_DIR",
default=None,
help="The location for configuration files like " "include.gypi.",
help="The location for configuration files like include.gypi.",
)
parser.add_argument(
"-d",
@ -474,8 +492,7 @@ def gyp_main(args):
options, build_files_arg = parser.parse_args(args)
if options.version:
import pkg_resources
print(f"v{pkg_resources.get_distribution('gyp-next').version}")
print(f"v{version('gyp-next')}")
return 0
build_files = build_files_arg
@ -510,19 +527,18 @@ def gyp_main(args):
# If no format was given on the command line, then check the env variable.
generate_formats = []
if options.use_environment:
generate_formats = os.environ.get("GYP_GENERATORS", [])
generate_formats = os.environ.get("GYP_GENERATORS") or []
if generate_formats:
generate_formats = re.split(r"[\s,]", generate_formats)
if generate_formats:
options.formats = generate_formats
# Nothing in the variable, default based on platform.
elif sys.platform == "darwin":
options.formats = ["xcode"]
elif sys.platform in ("win32", "cygwin"):
options.formats = ["msvs"]
else:
# Nothing in the variable, default based on platform.
if sys.platform == "darwin":
options.formats = ["xcode"]
elif sys.platform in ("win32", "cygwin"):
options.formats = ["msvs"]
else:
options.formats = ["make"]
options.formats = ["make"]
if not options.generator_output and options.use_environment:
g_o = os.environ.get("GYP_GENERATOR_OUTPUT")
@ -622,7 +638,7 @@ def gyp_main(args):
if options.generator_flags:
gen_flags += options.generator_flags
generator_flags = NameValueListToDict(gen_flags)
if DEBUG_GENERAL in gyp.debug.keys():
if DEBUG_GENERAL in gyp.debug:
DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags)
# Generate all requested formats (use a set in case we got one format request
@ -681,7 +697,7 @@ def main(args):
return 1
# NOTE: setuptools generated console_scripts calls function with no arguments
# NOTE: console_scripts calls this function with no arguments
def script_main():
return main(sys.argv[1:])

View file

@ -6,10 +6,10 @@ import errno
import filecmp
import os.path
import re
import tempfile
import sys
import shlex
import subprocess
import sys
import tempfile
from collections.abc import MutableSet
@ -31,10 +31,8 @@ class memoize:
class GypError(Exception):
"""Error class representing an error, which is to be presented
to the user. The main entry point will catch and display this.
"""
pass
to the user. The main entry point will catch and display this.
"""
def ExceptionAppend(e, msg):
@ -49,9 +47,9 @@ def ExceptionAppend(e, msg):
def FindQualifiedTargets(target, qualified_list):
"""
Given a list of qualified targets, return the qualified targets for the
specified |target|.
"""
Given a list of qualified targets, return the qualified targets for the
specified |target|.
"""
return [t for t in qualified_list if ParseQualifiedTarget(t)[1] == target]
@ -116,7 +114,7 @@ def BuildFile(fully_qualified_target):
def GetEnvironFallback(var_list, default):
"""Look up a key in the environment, with fallback to secondary keys
and finally falling back to a default value."""
and finally falling back to a default value."""
for var in var_list:
if var in os.environ:
return os.environ[var]
@ -144,20 +142,16 @@ def RelativePath(path, relative_to, follow_path_symlink=True):
# symlink, this option has no effect.
# Convert to normalized (and therefore absolute paths).
if follow_path_symlink:
path = os.path.realpath(path)
else:
path = os.path.abspath(path)
path = os.path.realpath(path) if follow_path_symlink else os.path.abspath(path)
relative_to = os.path.realpath(relative_to)
# On Windows, we can't create a relative path to a different drive, so just
# use the absolute path.
if sys.platform == "win32":
if (
os.path.splitdrive(path)[0].lower()
!= os.path.splitdrive(relative_to)[0].lower()
):
return path
if sys.platform == "win32" and (
os.path.splitdrive(path)[0].lower()
!= os.path.splitdrive(relative_to)[0].lower()
):
return path
# Split the paths into components.
path_split = path.split(os.path.sep)
@ -183,11 +177,11 @@ def RelativePath(path, relative_to, follow_path_symlink=True):
@memoize
def InvertRelativePath(path, toplevel_dir=None):
"""Given a path like foo/bar that is relative to toplevel_dir, return
the inverse relative path back to the toplevel_dir.
the inverse relative path back to the toplevel_dir.
E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
should always produce the empty string, unless the path contains symlinks.
"""
E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
should always produce the empty string, unless the path contains symlinks.
"""
if not path:
return path
toplevel_dir = "." if toplevel_dir is None else toplevel_dir
@ -267,20 +261,17 @@ _escape = re.compile(r'(["\\`])')
def EncodePOSIXShellArgument(argument):
"""Encodes |argument| suitably for consumption by POSIX shells.
argument may be quoted and escaped as necessary to ensure that POSIX shells
treat the returned value as a literal representing the argument passed to
this function. Parameter (variable) expansions beginning with $ are allowed
to remain intact without escaping the $, to allow the argument to contain
references to variables to be expanded by the shell.
"""
argument may be quoted and escaped as necessary to ensure that POSIX shells
treat the returned value as a literal representing the argument passed to
this function. Parameter (variable) expansions beginning with $ are allowed
to remain intact without escaping the $, to allow the argument to contain
references to variables to be expanded by the shell.
"""
if not isinstance(argument, str):
argument = str(argument)
if _quote.search(argument):
quote = '"'
else:
quote = ""
quote = '"' if _quote.search(argument) else ""
encoded = quote + re.sub(_escape, r"\\\1", argument) + quote
@ -290,9 +281,9 @@ def EncodePOSIXShellArgument(argument):
def EncodePOSIXShellList(list):
"""Encodes |list| suitably for consumption by POSIX shells.
Returns EncodePOSIXShellArgument for each item in list, and joins them
together using the space character as an argument separator.
"""
Returns EncodePOSIXShellArgument for each item in list, and joins them
together using the space character as an argument separator.
"""
encoded_arguments = []
for argument in list:
@ -320,14 +311,12 @@ def DeepDependencyTargets(target_dicts, roots):
def BuildFileTargets(target_list, build_file):
"""From a target_list, returns the subset from the specified build_file.
"""
"""From a target_list, returns the subset from the specified build_file."""
return [p for p in target_list if BuildFile(p) == build_file]
def AllTargets(target_list, target_dicts, build_file):
"""Returns all targets (direct and dependencies) for the specified build_file.
"""
"""Returns all targets (direct and dependencies) for the specified build_file."""
bftargets = BuildFileTargets(target_list, build_file)
deptargets = DeepDependencyTargets(target_dicts, bftargets)
return bftargets + deptargets
@ -336,12 +325,12 @@ def AllTargets(target_list, target_dicts, build_file):
def WriteOnDiff(filename):
"""Write to a file only if the new contents differ.
Arguments:
filename: name of the file to potentially write to.
Returns:
A file like object which will write to temporary file and only overwrite
the target if it differs (on close).
"""
Arguments:
filename: name of the file to potentially write to.
Returns:
A file like object which will write to temporary file and only overwrite
the target if it differs (on close).
"""
class Writer:
"""Wrapper around file which only covers the target if it differs."""
@ -430,7 +419,70 @@ def EnsureDirExists(path):
pass
def GetFlavor(params):
def GetCompilerPredefines(): # -> dict
cmd = []
defines = {}
# shlex.split() will eat '\' in posix mode, but
# setting posix=False will preserve extra '"' cause CreateProcess fail on Windows
# this makes '\' in %CC_target% and %CFLAGS% work
def replace_sep(s):
return s.replace(os.sep, "/") if os.sep != "/" else s
if CC := os.environ.get("CC_target") or os.environ.get("CC"):
cmd += shlex.split(replace_sep(CC))
if CFLAGS := os.environ.get("CFLAGS"):
cmd += shlex.split(replace_sep(CFLAGS))
elif CXX := os.environ.get("CXX_target") or os.environ.get("CXX"):
cmd += shlex.split(replace_sep(CXX))
if CXXFLAGS := os.environ.get("CXXFLAGS"):
cmd += shlex.split(replace_sep(CXXFLAGS))
else:
return defines
if sys.platform == "win32":
fd, input = tempfile.mkstemp(suffix=".c")
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
try:
os.close(fd)
stdout = subprocess.run(
real_cmd, shell=True, capture_output=True, check=True
).stdout
except subprocess.CalledProcessError as e:
print(
"Warning: failed to get compiler predefines\n"
"cmd: %s\n"
"status: %d" % (e.cmd, e.returncode),
file=sys.stderr,
)
return defines
finally:
os.unlink(input)
else:
input = "/dev/null"
real_cmd = [*cmd, "-dM", "-E", "-x", "c", input]
try:
stdout = subprocess.run(
real_cmd, shell=False, capture_output=True, check=True
).stdout
except subprocess.CalledProcessError as e:
print(
"Warning: failed to get compiler predefines\n"
"cmd: %s\n"
"status: %d" % (e.cmd, e.returncode),
file=sys.stderr,
)
return defines
lines = stdout.decode("utf-8").replace("\r\n", "\n").split("\n")
for line in lines:
if (line or "").startswith("#define "):
_, key, *value = line.split(" ")
defines[key] = " ".join(value)
return defines
def GetFlavorByPlatform():
"""Returns |params.flavor| if it's set, the system's default flavor else."""
flavors = {
"cygwin": "win",
@ -438,8 +490,6 @@ def GetFlavor(params):
"darwin": "mac",
}
if "flavor" in params:
return params["flavor"]
if sys.platform in flavors:
return flavors[sys.platform]
if sys.platform.startswith("sunos"):
@ -460,9 +510,22 @@ def GetFlavor(params):
return "linux"
def GetFlavor(params):
if "flavor" in params:
return params["flavor"]
defines = GetCompilerPredefines()
if "__EMSCRIPTEN__" in defines:
return "emscripten"
if "__wasm__" in defines:
return "wasi" if "__wasi__" in defines else "wasm"
return GetFlavorByPlatform()
def CopyTool(flavor, out_path, generator_flags={}):
"""Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it
to |out_path|."""
to |out_path|."""
# aix and solaris just need flock emulation. mac and win use more complicated
# support scripts.
prefix = {
@ -518,7 +581,8 @@ def uniquer(seq, idfun=lambda x: x):
# Based on http://code.activestate.com/recipes/576694/.
class OrderedSet(MutableSet):
class OrderedSet(MutableSet): # noqa: PLW1641
# TODO (cclauss): Fix eq-without-hash ruff rule PLW1641
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
@ -596,24 +660,24 @@ class CycleError(Exception):
def TopologicallySorted(graph, get_edges):
r"""Topologically sort based on a user provided edge definition.
Args:
graph: A list of node names.
get_edges: A function mapping from node name to a hashable collection
of node names which this node has outgoing edges to.
Returns:
A list containing all of the node in graph in topological order.
It is assumed that calling get_edges once for each node and caching is
cheaper than repeatedly calling get_edges.
Raises:
CycleError in the event of a cycle.
Example:
graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
def GetEdges(node):
return re.findall(r'\$\(([^))]\)', graph[node])
print TopologicallySorted(graph.keys(), GetEdges)
==>
['a', 'c', b']
"""
Args:
graph: A list of node names.
get_edges: A function mapping from node name to a hashable collection
of node names which this node has outgoing edges to.
Returns:
A list containing all of the node in graph in topological order.
It is assumed that calling get_edges once for each node and caching is
cheaper than repeatedly calling get_edges.
Raises:
CycleError in the event of a cycle.
Example:
graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
def GetEdges(node):
return re.findall(r'\$\(([^))]\)', graph[node])
print TopologicallySorted(graph.keys(), GetEdges)
==>
['a', 'c', b']
"""
get_edges = memoize(get_edges)
visited = set()
visiting = set()

View file

@ -6,9 +6,13 @@
"""Unit tests for the common.py file."""
import gyp.common
import unittest
import os
import subprocess
import sys
import unittest
from unittest.mock import MagicMock, patch
import gyp.common
class TestTopologicallySorted(unittest.TestCase):
@ -24,9 +28,12 @@ class TestTopologicallySorted(unittest.TestCase):
def GetEdge(node):
return tuple(graph[node])
self.assertEqual(
gyp.common.TopologicallySorted(graph.keys(), GetEdge), ["a", "c", "d", "b"]
)
assert gyp.common.TopologicallySorted(graph.keys(), GetEdge) == [
"a",
"c",
"d",
"b",
]
def test_Cycle(self):
"""Test that an exception is thrown on a cyclic graph."""
@ -58,7 +65,7 @@ class TestGetFlavor(unittest.TestCase):
def assertFlavor(self, expected, argument, param):
sys.platform = argument
self.assertEqual(expected, gyp.common.GetFlavor(param))
assert expected == gyp.common.GetFlavor(param)
def test_platform_default(self):
self.assertFlavor("freebsd", "freebsd9", {})
@ -73,6 +80,107 @@ class TestGetFlavor(unittest.TestCase):
def test_param(self):
self.assertFlavor("foobar", "linux2", {"flavor": "foobar"})
class MockCommunicate:
def __init__(self, stdout):
self.stdout = stdout
def decode(self, encoding):
return self.stdout
@patch("os.close")
@patch("os.unlink")
@patch("tempfile.mkstemp")
def test_GetCompilerPredefines(self, mock_mkstemp, mock_unlink, mock_close):
mock_close.return_value = None
mock_unlink.return_value = None
mock_mkstemp.return_value = (0, "temp.c")
def mock_run(env, defines_stdout, expected_cmd, throws=False):
with patch("subprocess.run") as mock_run:
expected_input = "temp.c" if sys.platform == "win32" else "/dev/null"
if throws:
mock_run.side_effect = subprocess.CalledProcessError(
returncode=1,
cmd=[*expected_cmd, "-dM", "-E", "-x", "c", expected_input],
)
else:
mock_process = MagicMock()
mock_process.returncode = 0
mock_process.stdout = TestGetFlavor.MockCommunicate(defines_stdout)
mock_run.return_value = mock_process
with patch.dict(os.environ, env):
try:
defines = gyp.common.GetCompilerPredefines()
except Exception as e:
self.fail(f"GetCompilerPredefines raised an exception: {e}")
flavor = gyp.common.GetFlavor({})
if env.get("CC_target") or env.get("CC"):
mock_run.assert_called_with(
[*expected_cmd, "-dM", "-E", "-x", "c", expected_input],
shell=sys.platform == "win32",
capture_output=True,
check=True,
)
return [defines, flavor]
[defines0, _] = mock_run({"CC": "cl.exe"}, "", ["cl.exe"], True)
assert defines0 == {}
[defines1, _] = mock_run({}, "", [])
assert defines1 == {}
[defines2, flavor2] = mock_run(
{"CC_target": "/opt/wasi-sdk/bin/clang"},
"#define __wasm__ 1\n#define __wasi__ 1\n",
["/opt/wasi-sdk/bin/clang"],
)
assert defines2 == {"__wasm__": "1", "__wasi__": "1"}
assert flavor2 == "wasi"
[defines3, flavor3] = mock_run(
{"CC_target": "/opt/wasi-sdk/bin/clang --target=wasm32"},
"#define __wasm__ 1\n",
["/opt/wasi-sdk/bin/clang", "--target=wasm32"],
)
assert defines3 == {"__wasm__": "1"}
assert flavor3 == "wasm"
[defines4, flavor4] = mock_run(
{"CC_target": "/emsdk/upstream/emscripten/emcc"},
"#define __EMSCRIPTEN__ 1\n",
["/emsdk/upstream/emscripten/emcc"],
)
assert defines4 == {"__EMSCRIPTEN__": "1"}
assert flavor4 == "emscripten"
# Test path which include white space
[defines5, flavor5] = mock_run(
{
"CC_target": '"/Users/Toyo Li/wasi-sdk/bin/clang" -O3',
"CFLAGS": "--target=wasm32-wasi-threads -pthread",
},
"#define __wasm__ 1\n#define __wasi__ 1\n#define _REENTRANT 1\n",
[
"/Users/Toyo Li/wasi-sdk/bin/clang",
"-O3",
"--target=wasm32-wasi-threads",
"-pthread",
],
)
assert defines5 == {"__wasm__": "1", "__wasi__": "1", "_REENTRANT": "1"}
assert flavor5 == "wasi"
original_sep = os.sep
os.sep = "\\"
[defines6, flavor6] = mock_run(
{"CC_target": '"C:\\Program Files\\wasi-sdk\\clang.exe"'},
"#define __wasm__ 1\n#define __wasi__ 1\n",
["C:/Program Files/wasi-sdk/clang.exe"],
)
os.sep = original_sep
assert defines6 == {"__wasm__": "1", "__wasi__": "1"}
assert flavor6 == "wasi"
if __name__ == "__main__":
unittest.main()

View file

@ -2,51 +2,51 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import sys
import re
import os
import locale
import os
import re
import sys
from functools import reduce
def XmlToString(content, encoding="utf-8", pretty=False):
""" Writes the XML content to disk, touching the file only if it has changed.
"""Writes the XML content to disk, touching the file only if it has changed.
Visual Studio files have a lot of pre-defined structures. This function makes
it easy to represent these structures as Python data structures, instead of
having to create a lot of function calls.
Visual Studio files have a lot of pre-defined structures. This function makes
it easy to represent these structures as Python data structures, instead of
having to create a lot of function calls.
Each XML element of the content is represented as a list composed of:
1. The name of the element, a string,
2. The attributes of the element, a dictionary (optional), and
3+. The content of the element, if any. Strings are simple text nodes and
lists are child elements.
Each XML element of the content is represented as a list composed of:
1. The name of the element, a string,
2. The attributes of the element, a dictionary (optional), and
3+. The content of the element, if any. Strings are simple text nodes and
lists are child elements.
Example 1:
<test/>
becomes
['test']
Example 1:
<test/>
becomes
['test']
Example 2:
<myelement a='value1' b='value2'>
<childtype>This is</childtype>
<childtype>it!</childtype>
</myelement>
Example 2:
<myelement a='value1' b='value2'>
<childtype>This is</childtype>
<childtype>it!</childtype>
</myelement>
becomes
['myelement', {'a':'value1', 'b':'value2'},
['childtype', 'This is'],
['childtype', 'it!'],
]
becomes
['myelement', {'a':'value1', 'b':'value2'},
['childtype', 'This is'],
['childtype', 'it!'],
]
Args:
content: The structured content to be converted.
encoding: The encoding to report on the first XML line.
pretty: True if we want pretty printing with indents and new lines.
Args:
content: The structured content to be converted.
encoding: The encoding to report on the first XML line.
pretty: True if we want pretty printing with indents and new lines.
Returns:
The XML content as a string.
"""
Returns:
The XML content as a string.
"""
# We create a huge list of all the elements of the file.
xml_parts = ['<?xml version="1.0" encoding="%s"?>' % encoding]
if pretty:
@ -58,14 +58,14 @@ def XmlToString(content, encoding="utf-8", pretty=False):
def _ConstructContentList(xml_parts, specification, pretty, level=0):
""" Appends the XML parts corresponding to the specification.
"""Appends the XML parts corresponding to the specification.
Args:
xml_parts: A list of XML parts to be appended to.
specification: The specification of the element. See EasyXml docs.
pretty: True if we want pretty printing with indents and new lines.
level: Indentation level.
"""
Args:
xml_parts: A list of XML parts to be appended to.
specification: The specification of the element. See EasyXml docs.
pretty: True if we want pretty printing with indents and new lines.
level: Indentation level.
"""
# The first item in a specification is the name of the element.
if pretty:
indentation = " " * level
@ -107,21 +107,26 @@ def _ConstructContentList(xml_parts, specification, pretty, level=0):
xml_parts.append("/>%s" % new_line)
def WriteXmlIfChanged(content, path, encoding="utf-8", pretty=False,
win32=(sys.platform == "win32")):
""" Writes the XML content to disk, touching the file only if it has changed.
def WriteXmlIfChanged(
content, path, encoding="utf-8", pretty=False, win32=(sys.platform == "win32")
):
"""Writes the XML content to disk, touching the file only if it has changed.
Args:
content: The structured content to be written.
path: Location of the file.
encoding: The encoding to report on the first line of the XML file.
pretty: True if we want pretty printing with indents and new lines.
"""
Args:
content: The structured content to be written.
path: Location of the file.
encoding: The encoding to report on the first line of the XML file.
pretty: True if we want pretty printing with indents and new lines.
"""
xml_string = XmlToString(content, encoding, pretty)
if win32 and os.linesep != "\r\n":
xml_string = xml_string.replace("\n", "\r\n")
default_encoding = locale.getdefaultlocale()[1]
try: # getdefaultlocale() was removed in Python 3.11
default_encoding = locale.getdefaultlocale()[1]
except AttributeError:
default_encoding = locale.getencoding()
if default_encoding and default_encoding.upper() != encoding.upper():
xml_string = xml_string.encode(encoding)
@ -153,7 +158,7 @@ _xml_escape_re = re.compile("(%s)" % "|".join(map(re.escape, _xml_escape_map.key
def _XmlEscape(value, attr=False):
""" Escape a string for inclusion in XML."""
"""Escape a string for inclusion in XML."""
def replace(match):
m = match.string[match.start() : match.end()]

View file

@ -4,13 +4,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the easy_xml.py file. """
"""Unit tests for the easy_xml.py file."""
import gyp.easy_xml as easy_xml
import unittest
from io import StringIO
from gyp import easy_xml
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
@ -76,6 +76,8 @@ class TestSequenceFunctions(unittest.TestCase):
'\'Debug|Win32\'" Label="Configuration">'
"<ConfigurationType>Application</ConfigurationType>"
"<CharacterSet>Unicode</CharacterSet>"
"<SpectreMitigation>SpectreLoadCF</SpectreMitigation>"
"<VCToolsVersion>14.36.32532</VCToolsVersion>"
"</PropertyGroup>"
"</Project>"
)
@ -99,6 +101,8 @@ class TestSequenceFunctions(unittest.TestCase):
},
["ConfigurationType", "Application"],
["CharacterSet", "Unicode"],
["SpectreMitigation", "SpectreLoadCF"],
["VCToolsVersion", "14.36.32532"],
],
]
)

View file

@ -62,12 +62,12 @@ directly supplied to gyp. OTOH if both "a.gyp" and "b.gyp" are supplied to gyp
then the "all" target includes "b1" and "b2".
"""
import gyp.common
import json
import os
import posixpath
import gyp.common
debug = False
found_dependency_string = "Found dependency"
@ -129,8 +129,8 @@ def _ToGypPath(path):
def _ResolveParent(path, base_path_components):
"""Resolves |path|, which starts with at least one '../'. Returns an empty
string if the path shouldn't be considered. See _AddSources() for a
description of |base_path_components|."""
string if the path shouldn't be considered. See _AddSources() for a
description of |base_path_components|."""
depth = 0
while path.startswith("../"):
depth += 1
@ -150,14 +150,14 @@ def _ResolveParent(path, base_path_components):
def _AddSources(sources, base_path, base_path_components, result):
"""Extracts valid sources from |sources| and adds them to |result|. Each
source file is relative to |base_path|, but may contain '..'. To make
resolving '..' easier |base_path_components| contains each of the
directories in |base_path|. Additionally each source may contain variables.
Such sources are ignored as it is assumed dependencies on them are expressed
and tracked in some other means."""
source file is relative to |base_path|, but may contain '..'. To make
resolving '..' easier |base_path_components| contains each of the
directories in |base_path|. Additionally each source may contain variables.
Such sources are ignored as it is assumed dependencies on them are expressed
and tracked in some other means."""
# NOTE: gyp paths are always posix style.
for source in sources:
if not len(source) or source.startswith("!!!") or source.startswith("$"):
if not len(source) or source.startswith(("!!!", "$")):
continue
# variable expansion may lead to //.
org_source = source
@ -217,23 +217,23 @@ def _ExtractSources(target, target_dict, toplevel_dir):
class Target:
"""Holds information about a particular target:
deps: set of Targets this Target depends upon. This is not recursive, only the
direct dependent Targets.
match_status: one of the MatchStatus values.
back_deps: set of Targets that have a dependency on this Target.
visited: used during iteration to indicate whether we've visited this target.
This is used for two iterations, once in building the set of Targets and
again in _GetBuildTargets().
name: fully qualified name of the target.
requires_build: True if the target type is such that it needs to be built.
See _DoesTargetTypeRequireBuild for details.
added_to_compile_targets: used when determining if the target was added to the
set of targets that needs to be built.
in_roots: true if this target is a descendant of one of the root nodes.
is_executable: true if the type of target is executable.
is_static_library: true if the type of target is static_library.
is_or_has_linked_ancestor: true if the target does a link (eg executable), or
if there is a target in back_deps that does a link."""
deps: set of Targets this Target depends upon. This is not recursive, only the
direct dependent Targets.
match_status: one of the MatchStatus values.
back_deps: set of Targets that have a dependency on this Target.
visited: used during iteration to indicate whether we've visited this target.
This is used for two iterations, once in building the set of Targets and
again in _GetBuildTargets().
name: fully qualified name of the target.
requires_build: True if the target type is such that it needs to be built.
See _DoesTargetTypeRequireBuild for details.
added_to_compile_targets: used when determining if the target was added to the
set of targets that needs to be built.
in_roots: true if this target is a descendant of one of the root nodes.
is_executable: true if the type of target is executable.
is_static_library: true if the type of target is static_library.
is_or_has_linked_ancestor: true if the target does a link (eg executable), or
if there is a target in back_deps that does a link."""
def __init__(self, name):
self.deps = set()
@ -253,8 +253,8 @@ class Target:
class Config:
"""Details what we're looking for
files: set of files to search for
targets: see file description for details."""
files: set of files to search for
targets: see file description for details."""
def __init__(self):
self.files = []
@ -264,7 +264,7 @@ class Config:
def Init(self, params):
"""Initializes Config. This is a separate method as it raises an exception
if there is a parse error."""
if there is a parse error."""
generator_flags = params.get("generator_flags", {})
config_path = generator_flags.get("config_path", None)
if not config_path:
@ -288,8 +288,8 @@ class Config:
def _WasBuildFileModified(build_file, data, files, toplevel_dir):
"""Returns true if the build file |build_file| is either in |files| or
one of the files included by |build_file| is in |files|. |toplevel_dir| is
the root of the source tree."""
one of the files included by |build_file| is in |files|. |toplevel_dir| is
the root of the source tree."""
if _ToLocalPath(toplevel_dir, _ToGypPath(build_file)) in files:
if debug:
print("gyp file modified", build_file)
@ -318,8 +318,8 @@ def _WasBuildFileModified(build_file, data, files, toplevel_dir):
def _GetOrCreateTargetByName(targets, target_name):
"""Creates or returns the Target at targets[target_name]. If there is no
Target for |target_name| one is created. Returns a tuple of whether a new
Target was created and the Target."""
Target for |target_name| one is created. Returns a tuple of whether a new
Target was created and the Target."""
if target_name in targets:
return False, targets[target_name]
target = Target(target_name)
@ -339,13 +339,13 @@ def _DoesTargetTypeRequireBuild(target_dict):
def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build_files):
"""Returns a tuple of the following:
. A dictionary mapping from fully qualified name to Target.
. A list of the targets that have a source file in |files|.
. Targets that constitute the 'all' target. See description at top of file
for details on the 'all' target.
This sets the |match_status| of the targets that contain any of the source
files in |files| to MATCH_STATUS_MATCHES.
|toplevel_dir| is the root of the source tree."""
. A dictionary mapping from fully qualified name to Target.
. A list of the targets that have a source file in |files|.
. Targets that constitute the 'all' target. See description at top of file
for details on the 'all' target.
This sets the |match_status| of the targets that contain any of the source
files in |files| to MATCH_STATUS_MATCHES.
|toplevel_dir| is the root of the source tree."""
# Maps from target name to Target.
name_to_target = {}
@ -378,9 +378,10 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build
target_type = target_dicts[target_name]["type"]
target.is_executable = target_type == "executable"
target.is_static_library = target_type == "static_library"
target.is_or_has_linked_ancestor = (
target_type == "executable" or target_type == "shared_library"
)
target.is_or_has_linked_ancestor = target_type in {
"executable",
"shared_library",
}
build_file = gyp.common.ParseQualifiedTarget(target_name)[0]
if build_file not in build_file_in_files:
@ -426,34 +427,34 @@ def _GenerateTargets(data, target_list, target_dicts, toplevel_dir, files, build
def _GetUnqualifiedToTargetMapping(all_targets, to_find):
"""Returns a tuple of the following:
. mapping (dictionary) from unqualified name to Target for all the
Targets in |to_find|.
. any target names not found. If this is empty all targets were found."""
. mapping (dictionary) from unqualified name to Target for all the
Targets in |to_find|.
. any target names not found. If this is empty all targets were found."""
result = {}
if not to_find:
return {}, []
to_find = set(to_find)
for target_name in all_targets.keys():
for target_name in all_targets:
extracted = gyp.common.ParseQualifiedTarget(target_name)
if len(extracted) > 1 and extracted[1] in to_find:
to_find.remove(extracted[1])
result[extracted[1]] = all_targets[target_name]
if not to_find:
return result, []
return result, [x for x in to_find]
return result, list(to_find)
def _DoesTargetDependOnMatchingTargets(target):
"""Returns true if |target| or any of its dependencies is one of the
targets containing the files supplied as input to analyzer. This updates
|matches| of the Targets as it recurses.
target: the Target to look for."""
targets containing the files supplied as input to analyzer. This updates
|matches| of the Targets as it recurses.
target: the Target to look for."""
if target.match_status == MATCH_STATUS_DOESNT_MATCH:
return False
if (
target.match_status == MATCH_STATUS_MATCHES
or target.match_status == MATCH_STATUS_MATCHES_BY_DEPENDENCY
):
if target.match_status in {
MATCH_STATUS_MATCHES,
MATCH_STATUS_MATCHES_BY_DEPENDENCY,
}:
return True
for dep in target.deps:
if _DoesTargetDependOnMatchingTargets(dep):
@ -466,9 +467,9 @@ def _DoesTargetDependOnMatchingTargets(target):
def _GetTargetsDependingOnMatchingTargets(possible_targets):
"""Returns the list of Targets in |possible_targets| that depend (either
directly on indirectly) on at least one of the targets containing the files
supplied as input to analyzer.
possible_targets: targets to search from."""
directly on indirectly) on at least one of the targets containing the files
supplied as input to analyzer.
possible_targets: targets to search from."""
found = []
print("Targets that matched by dependency:")
for target in possible_targets:
@ -479,11 +480,11 @@ def _GetTargetsDependingOnMatchingTargets(possible_targets):
def _AddCompileTargets(target, roots, add_if_no_ancestor, result):
"""Recurses through all targets that depend on |target|, adding all targets
that need to be built (and are in |roots|) to |result|.
roots: set of root targets.
add_if_no_ancestor: If true and there are no ancestors of |target| then add
|target| to |result|. |target| must still be in |roots|.
result: targets that need to be built are added here."""
that need to be built (and are in |roots|) to |result|.
roots: set of root targets.
add_if_no_ancestor: If true and there are no ancestors of |target| then add
|target| to |result|. |target| must still be in |roots|.
result: targets that need to be built are added here."""
if target.visited:
return
@ -536,8 +537,8 @@ def _AddCompileTargets(target, roots, add_if_no_ancestor, result):
def _GetCompileTargets(matching_targets, supplied_targets):
"""Returns the set of Targets that require a build.
matching_targets: targets that changed and need to be built.
supplied_targets: set of targets supplied to analyzer to search from."""
matching_targets: targets that changed and need to be built.
supplied_targets: set of targets supplied to analyzer to search from."""
result = set()
for target in matching_targets:
print("finding compile targets for match", target.name)
@ -591,7 +592,7 @@ def _WriteOutput(params, **values):
def _WasGypIncludeFileModified(params, files):
"""Returns true if one of the files in |files| is in the set of included
files."""
files."""
if params["options"].includes:
for include in params["options"].includes:
if _ToGypPath(os.path.normpath(include)) in files:
@ -607,7 +608,7 @@ def _NamesNotIn(names, mapping):
def _LookupTargets(names, mapping):
"""Returns a list of the mapping[name] for each value in |names| that is in
|mapping|."""
|mapping|."""
return [mapping[name] for name in names if name in mapping]
@ -683,11 +684,9 @@ class TargetCalculator:
)
test_target_names_contains_all = "all" in self._test_target_names
if test_target_names_contains_all:
test_targets = [
x for x in (set(test_targets_no_all) | set(self._root_targets))
]
test_targets = list(set(test_targets_no_all) | set(self._root_targets))
else:
test_targets = [x for x in test_targets_no_all]
test_targets = list(test_targets_no_all)
print("supplied test_targets")
for target_name in self._test_target_names:
print("\t", target_name)
@ -701,10 +700,10 @@ class TargetCalculator:
) & set(self._root_targets)
if matching_test_targets_contains_all:
# Remove any of the targets for all that were not explicitly supplied,
# 'all' is subsequentely added to the matching names below.
matching_test_targets = [
x for x in (set(matching_test_targets) & set(test_targets_no_all))
]
# 'all' is subsequently added to the matching names below.
matching_test_targets = list(
set(matching_test_targets) & set(test_targets_no_all)
)
print("matched test_targets")
for target in matching_test_targets:
print("\t", target.name)
@ -729,9 +728,7 @@ class TargetCalculator:
self._supplied_target_names_no_all(), self._unqualified_mapping
)
if "all" in self._supplied_target_names():
supplied_targets = [
x for x in (set(supplied_targets) | set(self._root_targets))
]
supplied_targets = list(set(supplied_targets) | set(self._root_targets))
print("Supplied test_targets & compile_targets")
for target in supplied_targets:
print("\t", target.name)
@ -751,7 +748,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
if not config.files:
raise Exception(
"Must specify files to analyze via config_path generator " "flag"
"Must specify files to analyze via config_path generator flag"
)
toplevel_dir = _ToGypPath(os.path.abspath(params["options"].toplevel_dir))

View file

@ -15,13 +15,14 @@
# Try to avoid setting global variables where possible.
import gyp
import gyp.common
import gyp.generator.make as make # Reuse global functions from make backend.
import os
import re
import subprocess
import gyp
import gyp.common
from gyp.generator import make # Reuse global functions from make backend.
generator_default_variables = {
"OS": "android",
"EXECUTABLE_PREFIX": "",
@ -176,9 +177,7 @@ class AndroidMkWriter:
self.WriteLn("LOCAL_IS_HOST_MODULE := true")
self.WriteLn("LOCAL_MULTILIB := $(GYP_HOST_MULTILIB)")
elif sdk_version > 0:
self.WriteLn(
"LOCAL_MODULE_TARGET_ARCH := " "$(TARGET_$(GYP_VAR_PREFIX)ARCH)"
)
self.WriteLn("LOCAL_MODULE_TARGET_ARCH := $(TARGET_$(GYP_VAR_PREFIX)ARCH)")
self.WriteLn("LOCAL_SDK_VERSION := %s" % sdk_version)
# Grab output directories; needed for Actions and Rules.
@ -379,7 +378,7 @@ class AndroidMkWriter:
inputs = rule.get("inputs")
for rule_source in rule.get("rule_sources", []):
(rule_source_dirname, rule_source_basename) = os.path.split(rule_source)
(rule_source_root, rule_source_ext) = os.path.splitext(
(rule_source_root, _rule_source_ext) = os.path.splitext(
rule_source_basename
)
@ -587,11 +586,11 @@ class AndroidMkWriter:
local_files = []
for source in sources:
(root, ext) = os.path.splitext(source)
if "$(gyp_shared_intermediate_dir)" in source:
extra_sources.append(source)
elif "$(gyp_intermediate_dir)" in source:
extra_sources.append(source)
elif IsCPPExtension(ext) and ext != local_cpp_extension:
if (
"$(gyp_shared_intermediate_dir)" in source
or "$(gyp_intermediate_dir)" in source
or (IsCPPExtension(ext) and ext != local_cpp_extension)
):
extra_sources.append(source)
else:
local_files.append(os.path.normpath(os.path.join(self.path, source)))
@ -697,7 +696,7 @@ class AndroidMkWriter:
target,
)
if self.type != "static_library" and self.type != "shared_library":
if self.type not in {"static_library", "shared_library"}:
target_prefix = spec.get("product_prefix", target_prefix)
target = spec.get("product_name", target)
product_ext = spec.get("product_extension")
@ -730,19 +729,17 @@ class AndroidMkWriter:
path = "$($(GYP_HOST_VAR_PREFIX)HOST_OUT_INTERMEDIATE_LIBRARIES)"
else:
path = "$($(GYP_VAR_PREFIX)TARGET_OUT_INTERMEDIATE_LIBRARIES)"
# Other targets just get built into their intermediate dir.
elif self.toolset == "host":
path = (
"$(call intermediates-dir-for,%s,%s,true,,"
"$(GYP_HOST_VAR_PREFIX))" % (self.android_class, self.android_module)
)
else:
# Other targets just get built into their intermediate dir.
if self.toolset == "host":
path = (
"$(call intermediates-dir-for,%s,%s,true,,"
"$(GYP_HOST_VAR_PREFIX))"
% (self.android_class, self.android_module)
)
else:
path = "$(call intermediates-dir-for,{},{},,,$(GYP_VAR_PREFIX))".format(
self.android_class,
self.android_module,
)
path = (
f"$(call intermediates-dir-for,{self.android_class},"
f"{self.android_module},,,$(GYP_VAR_PREFIX))"
)
assert spec.get("product_dir") is None # TODO: not supported?
return os.path.join(path, self.ComputeOutputBasename(spec))
@ -769,7 +766,7 @@ class AndroidMkWriter:
Args:
cflags: A list of compiler flags, which may be mixed with "-I.."
Returns:
A tuple of lists: (clean_clfags, include_paths). "-I.." is trimmed.
A tuple of lists: (clean_cflags, include_paths). "-I.." is trimmed.
"""
clean_cflags = []
include_paths = []
@ -901,8 +898,7 @@ class AndroidMkWriter:
if self.type != "none":
self.WriteTargetFlags(spec, configs, link_deps)
settings = spec.get("aosp_build_settings", {})
if settings:
if settings := spec.get("aosp_build_settings", {}):
self.WriteLn("### Set directly by aosp_build_settings.")
for k, v in settings.items():
if isinstance(v, list):
@ -1003,9 +999,9 @@ class AndroidMkWriter:
# - i.e. that the resulting path is still inside the project tree. The
# path may legitimately have ended up containing just $(LOCAL_PATH), though,
# so we don't look for a slash.
assert local_path.startswith(
"$(LOCAL_PATH)"
), f"Path {path} attempts to escape from gyp path {self.path} !)"
assert local_path.startswith("$(LOCAL_PATH)"), (
f"Path {path} attempts to escape from gyp path {self.path} !)"
)
return local_path
def ExpandInputRoot(self, template, expansion, dirname):
@ -1047,9 +1043,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
base_path = gyp.common.RelativePath(os.path.dirname(build_file), options.depth)
# We write the file in the base_path directory.
output_file = os.path.join(options.depth, base_path, base_name)
assert (
not options.generator_output
), "The Android backend does not support options.generator_output."
assert not options.generator_output, (
"The Android backend does not support options.generator_output."
)
base_path = gyp.common.RelativePath(
os.path.dirname(build_file), options.toplevel_dir
)
@ -1069,9 +1065,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
makefile_name = "GypAndroid" + options.suffix + ".mk"
makefile_path = os.path.join(options.toplevel_dir, makefile_name)
assert (
not options.generator_output
), "The Android backend does not support options.generator_output."
assert not options.generator_output, (
"The Android backend does not support options.generator_output."
)
gyp.common.EnsureDirExists(makefile_path)
root_makefile = open(makefile_path, "w")

View file

@ -28,11 +28,11 @@ not be able to find the header file directories described in the generated
CMakeLists.txt file.
"""
import multiprocessing
import os
import signal
import subprocess
import gyp.common
import gyp.xcode_emulation
@ -96,14 +96,14 @@ def Linkable(filename):
def NormjoinPathForceCMakeSource(base_path, rel_path):
"""Resolves rel_path against base_path and returns the result.
If rel_path is an absolute path it is returned unchanged.
Otherwise it is resolved against base_path and normalized.
If the result is a relative path, it is forced to be relative to the
CMakeLists.txt.
"""
If rel_path is an absolute path it is returned unchanged.
Otherwise it is resolved against base_path and normalized.
If the result is a relative path, it is forced to be relative to the
CMakeLists.txt.
"""
if os.path.isabs(rel_path):
return rel_path
if any([rel_path.startswith(var) for var in FULL_PATH_VARS]):
if any(rel_path.startswith(var) for var in FULL_PATH_VARS):
return rel_path
# TODO: do we need to check base_path for absolute variables as well?
return os.path.join(
@ -113,10 +113,10 @@ def NormjoinPathForceCMakeSource(base_path, rel_path):
def NormjoinPath(base_path, rel_path):
"""Resolves rel_path against base_path and returns the result.
TODO: what is this really used for?
If rel_path begins with '$' it is returned unchanged.
Otherwise it is resolved against base_path if relative, then normalized.
"""
TODO: what is this really used for?
If rel_path begins with '$' it is returned unchanged.
Otherwise it is resolved against base_path if relative, then normalized.
"""
if rel_path.startswith("$") and not rel_path.startswith("${configuration}"):
return rel_path
return os.path.normpath(os.path.join(base_path, rel_path))
@ -125,19 +125,19 @@ def NormjoinPath(base_path, rel_path):
def CMakeStringEscape(a):
"""Escapes the string 'a' for use inside a CMake string.
This means escaping
'\' otherwise it may be seen as modifying the next character
'"' otherwise it will end the string
';' otherwise the string becomes a list
This means escaping
'\' otherwise it may be seen as modifying the next character
'"' otherwise it will end the string
';' otherwise the string becomes a list
The following do not need to be escaped
'#' when the lexer is in string state, this does not start a comment
The following do not need to be escaped
'#' when the lexer is in string state, this does not start a comment
The following are yet unknown
'$' generator variables (like ${obj}) must not be escaped,
but text $ should be escaped
what is wanted is to know which $ come from generator variables
"""
The following are yet unknown
'$' generator variables (like ${obj}) must not be escaped,
but text $ should be escaped
what is wanted is to know which $ come from generator variables
"""
return a.replace("\\", "\\\\").replace(";", "\\;").replace('"', '\\"')
@ -236,25 +236,25 @@ cmake_target_type_from_gyp_target_type = {
def StringToCMakeTargetName(a):
"""Converts the given string 'a' to a valid CMake target name.
All invalid characters are replaced by '_'.
Invalid for cmake: ' ', '/', '(', ')', '"'
Invalid for make: ':'
Invalid for unknown reasons but cause failures: '.'
"""
All invalid characters are replaced by '_'.
Invalid for cmake: ' ', '/', '(', ')', '"'
Invalid for make: ':'
Invalid for unknown reasons but cause failures: '.'
"""
return a.translate(_maketrans(' /():."', "_______"))
def WriteActions(target_name, actions, extra_sources, extra_deps, path_to_gyp, output):
"""Write CMake for the 'actions' in the target.
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_taget>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_target>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
for action in actions:
action_name = StringToCMakeTargetName(action["action_name"])
action_target_name = f"{target_name}__{action_name}"
@ -328,7 +328,7 @@ def WriteActions(target_name, actions, extra_sources, extra_deps, path_to_gyp, o
def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
if rel_path.startswith(("${RULE_INPUT_PATH}", "${RULE_INPUT_DIRNAME}")):
if any([rule_source.startswith(var) for var in FULL_PATH_VARS]):
if any(rule_source.startswith(var) for var in FULL_PATH_VARS):
return rel_path
return NormjoinPathForceCMakeSource(base_path, rel_path)
@ -336,14 +336,14 @@ def NormjoinRulePathForceCMakeSource(base_path, rel_path, rule_source):
def WriteRules(target_name, rules, extra_sources, extra_deps, path_to_gyp, output):
"""Write CMake for the 'rules' in the target.
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_taget>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_sources: [(<cmake_src>, <src>)] to append with generated source files.
extra_deps: [<cmake_target>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
for rule in rules:
rule_name = StringToCMakeTargetName(target_name + "__" + rule["rule_name"])
@ -454,13 +454,13 @@ def WriteRules(target_name, rules, extra_sources, extra_deps, path_to_gyp, outpu
def WriteCopies(target_name, copies, extra_deps, path_to_gyp, output):
"""Write CMake for the 'copies' in the target.
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_deps: [<cmake_taget>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
Args:
target_name: the name of the CMake target being generated.
actions: the Gyp 'actions' dict for this target.
extra_deps: [<cmake_target>] to append with generated targets.
path_to_gyp: relative path from CMakeLists.txt being generated to
the Gyp file in which the target being generated is defined.
"""
copy_name = target_name + "__copies"
# CMake gets upset with custom targets with OUTPUT which specify no output.
@ -584,26 +584,26 @@ def CreateCMakeTargetFullName(qualified_target):
class CMakeNamer:
"""Converts Gyp target names into CMake target names.
CMake requires that target names be globally unique. One way to ensure
this is to fully qualify the names of the targets. Unfortunately, this
ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
of just "chrome". If this generator were only interested in building, it
would be possible to fully qualify all target names, then create
unqualified target names which depend on all qualified targets which
should have had that name. This is more or less what the 'make' generator
does with aliases. However, one goal of this generator is to create CMake
files for use with IDEs, and fully qualified names are not as user
friendly.
CMake requires that target names be globally unique. One way to ensure
this is to fully qualify the names of the targets. Unfortunately, this
ends up with all targets looking like "chrome_chrome_gyp_chrome" instead
of just "chrome". If this generator were only interested in building, it
would be possible to fully qualify all target names, then create
unqualified target names which depend on all qualified targets which
should have had that name. This is more or less what the 'make' generator
does with aliases. However, one goal of this generator is to create CMake
files for use with IDEs, and fully qualified names are not as user
friendly.
Since target name collision is rare, we do the above only when required.
Since target name collision is rare, we do the above only when required.
Toolset variants are always qualified from the base, as this is required for
building. However, it also makes sense for an IDE, as it is possible for
defines to be different.
"""
Toolset variants are always qualified from the base, as this is required for
building. However, it also makes sense for an IDE, as it is possible for
defines to be different.
"""
def __init__(self, target_list):
self.cmake_target_base_names_conficting = set()
self.cmake_target_base_names_conflicting = set()
cmake_target_base_names_seen = set()
for qualified_target in target_list:
@ -612,11 +612,11 @@ class CMakeNamer:
if cmake_target_base_name not in cmake_target_base_names_seen:
cmake_target_base_names_seen.add(cmake_target_base_name)
else:
self.cmake_target_base_names_conficting.add(cmake_target_base_name)
self.cmake_target_base_names_conflicting.add(cmake_target_base_name)
def CreateCMakeTargetName(self, qualified_target):
base_name = CreateCMakeTargetBaseName(qualified_target)
if base_name in self.cmake_target_base_names_conficting:
if base_name in self.cmake_target_base_names_conflicting:
return CreateCMakeTargetFullName(qualified_target)
return base_name
@ -809,8 +809,7 @@ def WriteTarget(
# link directories to targets defined after it is called.
# As a result, link_directories must come before the target definition.
# CMake unfortunately has no means of removing entries from LINK_DIRECTORIES.
library_dirs = config.get("library_dirs")
if library_dirs is not None:
if (library_dirs := config.get("library_dirs")) is not None:
output.write("link_directories(")
for library_dir in library_dirs:
output.write(" ")
@ -929,10 +928,7 @@ def WriteTarget(
product_prefix = spec.get("product_prefix", default_product_prefix)
product_name = spec.get("product_name", default_product_name)
product_ext = spec.get("product_extension")
if product_ext:
product_ext = "." + product_ext
else:
product_ext = default_product_ext
product_ext = "." + product_ext if product_ext else default_product_ext
SetTargetProperty(output, cmake_target_name, "PREFIX", product_prefix)
SetTargetProperty(
@ -1297,8 +1293,7 @@ def CallGenerateOutputForConfig(arglist):
def GenerateOutput(target_list, target_dicts, data, params):
user_config = params.get("generator_flags", {}).get("config", None)
if user_config:
if user_config := params.get("generator_flags", {}).get("config", None):
GenerateOutputForConfig(target_list, target_dicts, data, params, user_config)
else:
config_names = target_dicts[target_list[0]]["configurations"]

View file

@ -2,11 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import gyp.common
import gyp.xcode_emulation
import json
import os
import gyp.common
import gyp.xcode_emulation
generator_additional_non_configuration_keys = []
generator_additional_path_sections = []
generator_extra_sources_for_rules = []
@ -34,7 +35,7 @@ generator_default_variables = {
def IsMac(params):
return "mac" == gyp.common.GetFlavor(params)
return gyp.common.GetFlavor(params) == "mac"
def CalculateVariables(default_variables, params):
@ -93,13 +94,13 @@ def AddCommandsForTarget(cwd, target, params, per_config_commands):
gyp.common.EncodePOSIXShellArgument(file),
)
)
commands.append(dict(command=command, directory=output_dir, file=file))
commands.append({"command": command, "directory": output_dir, "file": file})
def GenerateOutput(target_list, target_dicts, data, params):
per_config_commands = {}
for qualified_target, target in target_dicts.items():
build_file, target_name, toolset = gyp.common.ParseQualifiedTarget(
build_file, _target_name, _toolset = gyp.common.ParseQualifiedTarget(
qualified_target
)
if IsMac(params):
@ -108,7 +109,14 @@ def GenerateOutput(target_list, target_dicts, data, params):
cwd = os.path.dirname(build_file)
AddCommandsForTarget(cwd, target, params, per_config_commands)
output_dir = params["generator_flags"].get("output_dir", "out")
output_dir = None
try:
# generator_output can be `None` on Windows machines, or even not
# defined in other cases
output_dir = params.get("options").generator_output
except AttributeError:
pass
output_dir = output_dir or params["generator_flags"].get("output_dir", "out")
for configuration_name, commands in per_config_commands.items():
filename = os.path.join(output_dir, configuration_name, "compile_commands.json")
gyp.common.EnsureDirExists(filename)

View file

@ -3,11 +3,12 @@
# found in the LICENSE file.
import json
import os
import gyp
import gyp.common
import gyp.msvs_emulation
import json
generator_supports_multiple_toolsets = True
@ -55,7 +56,7 @@ def CalculateVariables(default_variables, params):
def CalculateGeneratorInputInfo(params):
"""Calculate the generator specific info that gets fed to input (called by
gyp)."""
gyp)."""
generator_flags = params.get("generator_flags", {})
if generator_flags.get("adjust_static_libraries", False):
global generator_wants_static_library_dependencies_adjusted

View file

@ -17,14 +17,15 @@ still result in a few indexer issues here and there.
This generator has no automated tests, so expect it to be broken.
"""
from xml.sax.saxutils import escape
import os.path
import shlex
import subprocess
import xml.etree.ElementTree as ET
from xml.sax.saxutils import escape
import gyp
import gyp.common
import gyp.msvs_emulation
import shlex
import xml.etree.ElementTree as ET
generator_wants_static_library_dependencies_adjusted = False
@ -68,7 +69,7 @@ def CalculateVariables(default_variables, params):
def CalculateGeneratorInputInfo(params):
"""Calculate the generator specific info that gets fed to input (called by
gyp)."""
gyp)."""
generator_flags = params.get("generator_flags", {})
if generator_flags.get("adjust_static_libraries", False):
global generator_wants_static_library_dependencies_adjusted
@ -85,10 +86,10 @@ def GetAllIncludeDirectories(
):
"""Calculate the set of include directories to be used.
Returns:
A list including all the include_dir's specified for every target followed
by any include directories that were added as cflag compiler options.
"""
Returns:
A list including all the include_dir's specified for every target followed
by any include directories that were added as cflag compiler options.
"""
gyp_includes_set = set()
compiler_includes_list = []
@ -177,11 +178,11 @@ def GetAllIncludeDirectories(
def GetCompilerPath(target_list, data, options):
"""Determine a command that can be used to invoke the compiler.
Returns:
If this is a gyp project that has explicit make settings, try to determine
the compiler from that. Otherwise, see if a compiler was specified via the
CC_target environment variable.
"""
Returns:
If this is a gyp project that has explicit make settings, try to determine
the compiler from that. Otherwise, see if a compiler was specified via the
CC_target environment variable.
"""
# First, see if the compiler is configured in make's settings.
build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
make_global_settings_dict = data[build_file].get("make_global_settings", {})
@ -201,10 +202,10 @@ def GetCompilerPath(target_list, data, options):
def GetAllDefines(target_list, target_dicts, data, config_name, params, compiler_path):
"""Calculate the defines for a project.
Returns:
A dict that includes explicit defines declared in gyp files along with all
of the default defines that the compiler uses.
"""
Returns:
A dict that includes explicit defines declared in gyp files along with all
of the default defines that the compiler uses.
"""
# Get defines declared in the gyp files.
all_defines = {}
@ -248,10 +249,7 @@ def GetAllDefines(target_list, target_dicts, data, config_name, params, compiler
continue
cpp_line_parts = cpp_line.split(" ", 2)
key = cpp_line_parts[1]
if len(cpp_line_parts) >= 3:
val = cpp_line_parts[2]
else:
val = "1"
val = cpp_line_parts[2] if len(cpp_line_parts) >= 3 else "1"
all_defines[key] = val
return all_defines
@ -375,8 +373,8 @@ def GenerateClasspathFile(
target_list, target_dicts, toplevel_dir, toplevel_build, out_name
):
"""Generates a classpath file suitable for symbol navigation and code
completion of Java code (such as in Android projects) by finding all
.java and .jar files used as action inputs."""
completion of Java code (such as in Android projects) by finding all
.java and .jar files used as action inputs."""
gyp.common.EnsureDirExists(out_name)
result = ET.Element("classpath")
@ -453,8 +451,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
if params["options"].generator_output:
raise NotImplementedError("--generator_output not implemented for eclipse")
user_config = params.get("generator_flags", {}).get("config", None)
if user_config:
if user_config := params.get("generator_flags", {}).get("config", None):
GenerateOutputForConfig(target_list, target_dicts, data, params, user_config)
else:
config_names = target_dicts[target_list[0]]["configurations"]

View file

@ -30,10 +30,9 @@ The specific formatting of the output generated by this module is subject
to change.
"""
import gyp.common
import pprint
import gyp.common
# These variables should just be spit back out as variable references.
_generator_identity_variables = [
@ -74,7 +73,7 @@ for v in _generator_identity_variables:
def GenerateOutput(target_list, target_dicts, data, params):
output_files = {}
for qualified_target in target_list:
[input_file, target] = gyp.common.ParseQualifiedTarget(qualified_target)[0:2]
[input_file, _target] = gyp.common.ParseQualifiedTarget(qualified_target)[0:2]
if input_file[-4:] != ".gyp":
continue

View file

@ -13,11 +13,9 @@ by the input module.
The expected usage is "gyp -f gypsh -D OS=desired_os".
"""
import code
import sys
# All of this stuff about generator variables was lovingly ripped from gypd.py.
# That module has a much better description of what's going on and why.
_generator_identity_variables = [
@ -49,10 +47,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
# Use a banner that looks like the stock Python one and like what
# code.interact uses by default, but tack on something to indicate what
# locals are available, and identify gypsh.
banner = "Python {} on {}\nlocals.keys() = {}\ngypsh".format(
sys.version,
sys.platform,
repr(sorted(locals.keys())),
banner = (
f"Python {sys.version} on {sys.platform}\nlocals.keys() = "
f"{sorted(locals.keys())!r}\ngypsh"
)
code.interact(banner, local=locals)

View file

@ -22,16 +22,17 @@
# the side to keep the files readable.
import hashlib
import os
import re
import subprocess
import sys
import gyp
import gyp.common
import gyp.xcode_emulation
from gyp.common import GetEnvironFallback
import hashlib
generator_default_variables = {
"EXECUTABLE_PREFIX": "",
"EXECUTABLE_SUFFIX": "",
@ -77,7 +78,7 @@ def CalculateVariables(default_variables, params):
# Copy additional generator configuration data from Xcode, which is shared
# by the Mac Make generator.
import gyp.generator.xcode as xcode_generator
import gyp.generator.xcode as xcode_generator # noqa: PLC0415
global generator_additional_non_configuration_keys
generator_additional_non_configuration_keys = getattr(
@ -207,7 +208,7 @@ cmd_solink_module = $(LINK.$(TOOLSET)) -o $@ -shared $(GYP_LDFLAGS) $(LDFLAGS.$(
LINK_COMMANDS_MAC = """\
quiet_cmd_alink = LIBTOOL-STATIC $@
cmd_alink = rm -f $@ && ./gyp-mac-tool filter-libtool libtool $(GYP_LIBTOOLFLAGS) -static -o $@ $(filter %.o,$^)
cmd_alink = rm -f $@ && %(python)s gyp-mac-tool filter-libtool libtool $(GYP_LIBTOOLFLAGS) -static -o $@ $(filter %%.o,$^)
quiet_cmd_link = LINK($(TOOLSET)) $@
cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o "$@" $(LD_INPUTS) $(LIBS)
@ -217,7 +218,7 @@ cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o
quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
cmd_solink_module = $(LINK.$(TOOLSET)) -bundle $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ $(filter-out FORCE_DO_CMD, $^) $(LIBS)
""" # noqa: E501
""" % {"python": sys.executable} # noqa: E501
LINK_COMMANDS_ANDROID = """\
quiet_cmd_alink = AR($(TOOLSET)) $@
@ -378,7 +379,7 @@ CXX.target ?= %(CXX.target)s
CXXFLAGS.target ?= $(CPPFLAGS) $(CXXFLAGS)
LINK.target ?= %(LINK.target)s
LDFLAGS.target ?= $(LDFLAGS)
AR.target ?= $(AR)
AR.target ?= %(AR.target)s
PLI.target ?= %(PLI.target)s
# C++ apps need to be linked with g++.
@ -442,13 +443,27 @@ DEPFLAGS = %(makedep_args)s -MF $(depfile).raw
define fixup_dep
# The depfile may not exist if the input file didn't have any #includes.
touch $(depfile).raw
# Fixup path as in (1).
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)
# Fixup path as in (1)."""
+ (
r"""
sed -e "s|^$(notdir $@)|$@|" -re 's/\\\\([^$$])/\/\1/g' $(depfile).raw >> $(depfile)"""
if sys.platform == "win32"
else r"""
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)"""
)
+ r"""
# Add extra rules as in (2).
# We remove slashes and replace spaces with new lines;
# remove blank lines;
# delete the first line and append a colon to the remaining lines.
sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\
# delete the first line and append a colon to the remaining lines."""
+ (
"""
sed -e 's/\\\\\\\\$$//' -e 's/\\\\\\\\/\\//g' -e 'y| |\\n|' $(depfile).raw |\\"""
if sys.platform == "win32"
else """
sed -e 's|\\\\||' -e 'y| |\\n|' $(depfile).raw |\\"""
)
+ r"""
grep -v '^$$' |\
sed -e 1d -e 's|$$|:|' \
>> $(depfile)
@ -600,14 +615,14 @@ cmd_pch_mm = $(CC.$(TOOLSET)) $(GYP_PCH_OBJCXXFLAGS) $(DEPFLAGS) -c -o $@ $<
# Use $(4) for the command, since $(2) and $(3) are used as flag by do_cmd
# already.
quiet_cmd_mac_tool = MACTOOL $(4) $<
cmd_mac_tool = ./gyp-mac-tool $(4) $< "$@"
cmd_mac_tool = %(python)s gyp-mac-tool $(4) $< "$@"
quiet_cmd_mac_package_framework = PACKAGE FRAMEWORK $@
cmd_mac_package_framework = ./gyp-mac-tool package-framework "$@" $(4)
cmd_mac_package_framework = %(python)s gyp-mac-tool package-framework "$@" $(4)
quiet_cmd_infoplist = INFOPLIST $@
cmd_infoplist = $(CC.$(TOOLSET)) -E -P -Wno-trigraphs -x c $(INFOPLIST_DEFINES) "$<" -o "$@"
""" # noqa: E501
""" % {"python": sys.executable} # noqa: E501
def WriteRootHeaderSuffixRules(writer):
@ -681,10 +696,7 @@ COMPILABLE_EXTENSIONS = {
def Compilable(filename):
"""Return true if the file is compilable (should be in OBJS)."""
for res in (filename.endswith(e) for e in COMPILABLE_EXTENSIONS):
if res:
return True
return False
return any(res for res in (filename.endswith(e) for e in COMPILABLE_EXTENSIONS))
def Linkable(filename):
@ -728,6 +740,12 @@ def QuoteIfNecessary(string):
return string
def replace_sep(string):
if sys.platform == "win32":
string = string.replace("\\\\", "/").replace("\\", "/")
return string
def StringToMakefileVariable(string):
"""Convert a string to a value that is acceptable as a make variable name."""
return re.sub("[^a-zA-Z0-9_]", "_", string)
@ -778,7 +796,7 @@ class MakefileWriter:
self.suffix_rules_objdir2 = {}
# Generate suffix rules for all compilable extensions.
for ext in COMPILABLE_EXTENSIONS.keys():
for ext, value in COMPILABLE_EXTENSIONS.items():
# Suffix rules for source folder.
self.suffix_rules_srcdir.update(
{
@ -787,7 +805,7 @@ class MakefileWriter:
$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(srcdir)/%%%s FORCE_DO_CMD
\t@$(call do_cmd,%s,1)
"""
% (ext, COMPILABLE_EXTENSIONS[ext])
% (ext, value)
)
}
)
@ -800,7 +818,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(srcdir)/%%%s FORCE_DO_CMD
$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj).$(TOOLSET)/%%%s FORCE_DO_CMD
\t@$(call do_cmd,%s,1)
"""
% (ext, COMPILABLE_EXTENSIONS[ext])
% (ext, value)
)
}
)
@ -811,7 +829,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj).$(TOOLSET)/%%%s FORCE_DO_CMD
$(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
\t@$(call do_cmd,%s,1)
"""
% (ext, COMPILABLE_EXTENSIONS[ext])
% (ext, value)
)
}
)
@ -862,7 +880,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.output = self.ComputeMacBundleOutput(spec)
self.output_binary = self.ComputeMacBundleBinaryOutput(spec)
else:
self.output = self.output_binary = self.ComputeOutput(spec)
self.output = self.output_binary = replace_sep(self.ComputeOutput(spec))
self.is_standalone_static_library = bool(
spec.get("standalone_static_library", 0)
@ -988,7 +1006,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# sub-project dir (see test/subdirectory/gyptest-subdir-all.py).
self.WriteLn(
"export builddir_name ?= %s"
% os.path.join(os.path.dirname(output_filename), build_dir)
% replace_sep(os.path.join(os.path.dirname(output_filename), build_dir))
)
self.WriteLn(".PHONY: all")
self.WriteLn("all:")
@ -1066,7 +1084,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# libraries, but until everything is made cross-compile safe, also use
# target libraries.
# TODO(piman): when everything is cross-compile safe, remove lib.target
if self.flavor == "zos" or self.flavor == "aix":
if self.flavor in {"zos", "aix"}:
self.WriteLn(
"cmd_%s = LIBPATH=$(builddir)/lib.host:"
"$(builddir)/lib.target:$$LIBPATH; "
@ -1151,7 +1169,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
for rule_source in rule.get("rule_sources", []):
dirs = set()
(rule_source_dirname, rule_source_basename) = os.path.split(rule_source)
(rule_source_root, rule_source_ext) = os.path.splitext(
(rule_source_root, _rule_source_ext) = os.path.splitext(
rule_source_basename
)
@ -1429,9 +1447,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
for obj in objs:
assert " " not in obj, "Spaces in object filenames not supported (%s)" % obj
self.WriteLn(
"# Add to the list of files we specially track " "dependencies for."
)
self.WriteLn("# Add to the list of files we specially track dependencies for.")
self.WriteLn("all_deps += $(OBJS)")
self.WriteLn()
@ -1440,7 +1456,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.WriteMakeRule(
["$(OBJS)"],
deps,
comment="Make sure our dependencies are built " "before any of us.",
comment="Make sure our dependencies are built before any of us.",
order_only=True,
)
@ -1451,12 +1467,11 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.WriteMakeRule(
["$(OBJS)"],
extra_outputs,
comment="Make sure our actions/rules run " "before any of us.",
comment="Make sure our actions/rules run before any of us.",
order_only=True,
)
pchdeps = precompiled_header.GetObjDependencies(compilable, objs)
if pchdeps:
if pchdeps := precompiled_header.GetObjDependencies(compilable, objs):
self.WriteLn("# Dependencies from obj files to their precompiled headers")
for source, obj, gch in pchdeps:
self.WriteLn(f"{obj}: {gch}")
@ -1489,7 +1504,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"$(OBJS): GYP_OBJCFLAGS := "
"$(DEFS_$(BUILDTYPE)) "
"$(INCS_$(BUILDTYPE)) "
"%s " % precompiled_header.GetInclude("m")
"%s "
% precompiled_header.GetInclude("m")
+ "$(CFLAGS_$(BUILDTYPE)) "
"$(CFLAGS_C_$(BUILDTYPE)) "
"$(CFLAGS_OBJC_$(BUILDTYPE))"
@ -1498,7 +1514,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"$(OBJS): GYP_OBJCXXFLAGS := "
"$(DEFS_$(BUILDTYPE)) "
"$(INCS_$(BUILDTYPE)) "
"%s " % precompiled_header.GetInclude("mm")
"%s "
% precompiled_header.GetInclude("mm")
+ "$(CFLAGS_$(BUILDTYPE)) "
"$(CFLAGS_CC_$(BUILDTYPE)) "
"$(CFLAGS_OBJCC_$(BUILDTYPE))"
@ -1590,8 +1607,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
target_prefix = spec.get("product_prefix", target_prefix)
target = spec.get("product_name", target)
product_ext = spec.get("product_extension")
if product_ext:
if product_ext := spec.get("product_extension"):
target_ext = "." + product_ext
return target_prefix + target + target_ext
@ -1689,7 +1705,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.WriteMakeRule(
extra_outputs,
deps,
comment=("Preserve order dependency of " "special output on deps."),
comment=("Preserve order dependency of special output on deps."),
order_only=True,
)
@ -1728,7 +1744,8 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# into the link command, so we need lots of escaping.
ldflags.append(r"-Wl,-rpath=\$$ORIGIN/")
ldflags.append(r"-Wl,-rpath-link=\$(builddir)/")
library_dirs = config.get("library_dirs", [])
if library_dirs := config.get("library_dirs", []):
library_dirs = [Sourceify(self.Absolutify(i)) for i in library_dirs]
ldflags += [("-L%s" % library_dir) for library_dir in library_dirs]
self.WriteList(ldflags, "LDFLAGS_%s" % configname)
if self.flavor == "mac":
@ -1769,13 +1786,13 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# using ":=".
self.WriteSortedXcodeEnv(self.output, self.GetSortedXcodePostbuildEnv())
for configname in target_postbuilds:
for configname, value in target_postbuilds.items():
self.WriteLn(
"%s: TARGET_POSTBUILDS_%s := %s"
% (
QuoteSpaces(self.output),
configname,
gyp.common.EncodePOSIXShellList(target_postbuilds[configname]),
gyp.common.EncodePOSIXShellList(value),
)
)
@ -1824,7 +1841,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# Since this target depends on binary and resources which are in
# nested subfolders, the framework directory will be older than
# its dependencies usually. To prevent this rule from executing
# on every build (expensive, especially with postbuilds), expliclity
# on every build (expensive, especially with postbuilds), explicitly
# update the time on the framework directory.
self.WriteLn("\t@touch -c %s" % QuoteSpaces(self.output))
@ -1834,7 +1851,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"on the bundle, not the binary (target '%s')" % self.target
)
assert "product_dir" not in spec, (
"Postbuilds do not work with " "custom product_dir"
"Postbuilds do not work with custom product_dir"
)
if self.type == "executable":
@ -1871,7 +1888,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
self.flavor not in ("mac", "openbsd", "netbsd", "win")
and not self.is_standalone_static_library
):
if self.flavor in ("linux", "android"):
if self.flavor in ("linux", "android", "openharmony"):
self.WriteMakeRule(
[self.output_binary],
link_deps,
@ -1885,21 +1902,20 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
part_of_all,
postbuilds=postbuilds,
)
elif self.flavor in ("linux", "android", "openharmony"):
self.WriteMakeRule(
[self.output_binary],
link_deps,
actions=["$(call create_archive,$@,$^)"],
)
else:
if self.flavor in ("linux", "android"):
self.WriteMakeRule(
[self.output_binary],
link_deps,
actions=["$(call create_archive,$@,$^)"],
)
else:
self.WriteDoCmd(
[self.output_binary],
link_deps,
"alink",
part_of_all,
postbuilds=postbuilds,
)
self.WriteDoCmd(
[self.output_binary],
link_deps,
"alink",
part_of_all,
postbuilds=postbuilds,
)
elif self.type == "shared_library":
self.WriteLn(
"%s: LD_INPUTS := %s"
@ -1991,7 +2007,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
and "product_dir" not in spec
and self.toolset == "target"
):
# On macOS, products are created in install_path immediately.
# On mac, products are created in install_path immediately.
assert install_path == self.output, f"{install_path} != {self.output}"
# Point the target alias to the final binary output.
@ -2031,7 +2047,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
installable_deps.append(
self.GetUnversionedSidedeckFromSidedeck(install_path)
)
if self.output != self.alias and self.alias != self.target:
if self.alias not in (self.output, self.target):
self.WriteMakeRule(
[self.alias],
installable_deps,
@ -2066,7 +2082,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
"""
values = ""
if value_list:
value_list = [quoter(prefix + value) for value in value_list]
value_list = [replace_sep(quoter(prefix + value)) for value in value_list]
values = " \\\n\t" + " \\\n\t".join(value_list)
self.fp.write(f"{variable} :={values}\n\n")
@ -2153,7 +2169,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%%.o: $(obj)/%%%s FORCE_DO_CMD
# - The multi-output rule will have an do-nothing recipe.
# Hash the target name to avoid generating overlong filenames.
cmddigest = hashlib.sha1(
cmddigest = hashlib.sha256(
(command or self.target).encode("utf-8")
).hexdigest()
intermediate = "%s.intermediate" % cmddigest
@ -2372,9 +2388,15 @@ def WriteAutoRegenerationRule(params, root_makefile, makefile_name, build_files)
"\t$(call do_cmd,regen_makefile)\n\n"
% {
"makefile_name": makefile_name,
"deps": " ".join(SourceifyAndQuoteSpaces(bf) for bf in build_files),
"cmd": gyp.common.EncodePOSIXShellList(
[gyp_binary, "-fmake"] + gyp.RegenerateFlags(options) + build_files_args
"deps": replace_sep(
" ".join(sorted(SourceifyAndQuoteSpaces(bf) for bf in build_files))
),
"cmd": replace_sep(
gyp.common.EncodePOSIXShellList(
[gyp_binary, "-fmake"]
+ gyp.RegenerateFlags(options)
+ build_files_args
)
),
}
)
@ -2438,36 +2460,55 @@ def GenerateOutput(target_list, target_dicts, data, params):
makefile_path = os.path.join(
options.toplevel_dir, options.generator_output, makefile_name
)
srcdir = gyp.common.RelativePath(srcdir, options.generator_output)
srcdir = replace_sep(gyp.common.RelativePath(srcdir, options.generator_output))
srcdir_prefix = "$(srcdir)/"
flock_command = "flock"
copy_archive_arguments = "-af"
makedep_arguments = "-MMD"
# wasm-ld doesn't support --start-group/--end-group
link_commands = LINK_COMMANDS_LINUX
if flavor in ["wasi", "wasm"]:
link_commands = link_commands.replace(" -Wl,--start-group", "").replace(
" -Wl,--end-group", ""
)
CC_target = replace_sep(GetEnvironFallback(("CC_target", "CC"), "$(CC)"))
AR_target = replace_sep(GetEnvironFallback(("AR_target", "AR"), "$(AR)"))
CXX_target = replace_sep(GetEnvironFallback(("CXX_target", "CXX"), "$(CXX)"))
LINK_target = replace_sep(GetEnvironFallback(("LINK_target", "LINK"), "$(LINK)"))
PLI_target = replace_sep(GetEnvironFallback(("PLI_target", "PLI"), "pli"))
CC_host = replace_sep(GetEnvironFallback(("CC_host", "CC"), "gcc"))
AR_host = replace_sep(GetEnvironFallback(("AR_host", "AR"), "ar"))
CXX_host = replace_sep(GetEnvironFallback(("CXX_host", "CXX"), "g++"))
LINK_host = replace_sep(GetEnvironFallback(("LINK_host", "LINK"), "$(CXX.host)"))
PLI_host = replace_sep(GetEnvironFallback(("PLI_host", "PLI"), "pli"))
header_params = {
"default_target": default_target,
"builddir": builddir_name,
"default_configuration": default_configuration,
"flock": flock_command,
"flock_index": 1,
"link_commands": LINK_COMMANDS_LINUX,
"link_commands": link_commands,
"extra_commands": "",
"srcdir": srcdir,
"copy_archive_args": copy_archive_arguments,
"makedep_args": makedep_arguments,
"CC.target": GetEnvironFallback(("CC_target", "CC"), "$(CC)"),
"AR.target": GetEnvironFallback(("AR_target", "AR"), "$(AR)"),
"CXX.target": GetEnvironFallback(("CXX_target", "CXX"), "$(CXX)"),
"LINK.target": GetEnvironFallback(("LINK_target", "LINK"), "$(LINK)"),
"PLI.target": GetEnvironFallback(("PLI_target", "PLI"), "pli"),
"CC.host": GetEnvironFallback(("CC_host", "CC"), "gcc"),
"AR.host": GetEnvironFallback(("AR_host", "AR"), "ar"),
"CXX.host": GetEnvironFallback(("CXX_host", "CXX"), "g++"),
"LINK.host": GetEnvironFallback(("LINK_host", "LINK"), "$(CXX.host)"),
"PLI.host": GetEnvironFallback(("PLI_host", "PLI"), "pli"),
"CC.target": CC_target,
"AR.target": AR_target,
"CXX.target": CXX_target,
"LINK.target": LINK_target,
"PLI.target": PLI_target,
"CC.host": CC_host,
"AR.host": AR_host,
"CXX.host": CXX_host,
"LINK.host": LINK_host,
"PLI.host": PLI_host,
}
if flavor == "mac":
flock_command = "./gyp-mac-tool flock"
flock_command = "%s gyp-mac-tool flock" % sys.executable
header_params.update(
{
"flock": flock_command,
@ -2517,7 +2558,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
header_params.update(
{
"copy_archive_args": copy_archive_arguments,
"flock": "./gyp-flock-tool flock",
"flock": "%s gyp-flock-tool flock" % sys.executable,
"flock_index": 2,
}
)
@ -2533,7 +2574,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
{
"copy_archive_args": copy_archive_arguments,
"link_commands": LINK_COMMANDS_AIX,
"flock": "./gyp-flock-tool flock",
"flock": "%s gyp-flock-tool flock" % sys.executable,
"flock_index": 2,
}
)
@ -2543,7 +2584,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
{
"copy_archive_args": copy_archive_arguments,
"link_commands": LINK_COMMANDS_OS400,
"flock": "./gyp-flock-tool flock",
"flock": "%s gyp-flock-tool flock" % sys.executable,
"flock_index": 2,
}
)

File diff suppressed because it is too large Load diff

View file

@ -3,13 +3,13 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the msvs.py file. """
"""Unit tests for the msvs.py file."""
import gyp.generator.msvs as msvs
import unittest
from io import StringIO
from gyp.generator import msvs
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):

View file

@ -5,24 +5,24 @@
import collections
import copy
import ctypes
import hashlib
import json
import multiprocessing
import os.path
import re
import shutil
import signal
import subprocess
import sys
from io import StringIO
import gyp
import gyp.common
import gyp.msvs_emulation
import gyp.MSVSUtil as MSVSUtil
import gyp.xcode_emulation
from io import StringIO
from gyp import MSVSUtil, ninja_syntax
from gyp.common import GetEnvironFallback
import gyp.ninja_syntax as ninja_syntax
generator_default_variables = {
"EXECUTABLE_PREFIX": "",
@ -246,7 +246,7 @@ class NinjaWriter:
if flavor == "win":
# See docstring of msvs_emulation.GenerateEnvironmentFiles().
self.win_env = {}
for arch in ("x86", "x64"):
for arch in ("x86", "x64", "arm64"):
self.win_env[arch] = "environment." + arch
# Relative path from build output dir to base dir.
@ -264,8 +264,7 @@ class NinjaWriter:
dir.
"""
PRODUCT_DIR = "$!PRODUCT_DIR"
if PRODUCT_DIR in path:
if (PRODUCT_DIR := "$!PRODUCT_DIR") in path:
if product_dir:
path = path.replace(PRODUCT_DIR, product_dir)
else:
@ -273,8 +272,7 @@ class NinjaWriter:
path = path.replace(PRODUCT_DIR + "\\", "")
path = path.replace(PRODUCT_DIR, ".")
INTERMEDIATE_DIR = "$!INTERMEDIATE_DIR"
if INTERMEDIATE_DIR in path:
if (INTERMEDIATE_DIR := "$!INTERMEDIATE_DIR") in path:
int_dir = self.GypPathToUniqueOutput("gen")
# GypPathToUniqueOutput generates a path relative to the product dir,
# so insert product_dir in front if it is provided.
@ -811,9 +809,8 @@ class NinjaWriter:
outputs = [self.GypPathToNinja(o, env) for o in outputs]
if self.flavor == "win":
# WriteNewNinjaRule uses unique_name to create a rsp file on win.
extra_bindings.append(
("unique_name", hashlib.md5(outputs[0]).hexdigest())
)
unique_name = hashlib.sha256(outputs[0].encode("utf-8")).hexdigest()
extra_bindings.append(("unique_name", unique_name))
self.ninja.build(
outputs,
@ -1305,7 +1302,7 @@ class NinjaWriter:
ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)])
def WriteLink(self, spec, config_name, config, link_deps, compile_deps):
"""Write out a link step. Fills out target.binary. """
"""Write out a link step. Fills out target.binary."""
if self.flavor != "mac" or len(self.archs) == 1:
return self.WriteLinkForArch(
self.ninja, spec, config_name, config, link_deps, compile_deps
@ -1349,7 +1346,7 @@ class NinjaWriter:
def WriteLinkForArch(
self, ninja_file, spec, config_name, config, link_deps, compile_deps, arch=None
):
"""Write out a link step. Fills out target.binary. """
"""Write out a link step. Fills out target.binary."""
command = {
"executable": "link",
"loadable_module": "solink_module",
@ -1464,7 +1461,7 @@ class NinjaWriter:
# Respect environment variables related to build, but target-specific
# flags can still override them.
ldflags = env_ldflags + config.get("ldflags", [])
if is_executable and len(solibs):
if is_executable and solibs:
rpath = "lib/"
if self.toolset != "target":
rpath += self.toolset
@ -1554,7 +1551,7 @@ class NinjaWriter:
if pdbname:
output = [output, pdbname]
if len(solibs):
if solibs:
extra_bindings.append(
("solibs", gyp.common.EncodePOSIXShellList(sorted(solibs)))
)
@ -1757,11 +1754,9 @@ class NinjaWriter:
+ " && ".join([ninja_syntax.escape(command) for command in postbuilds])
)
command_string = (
commands
+ "); G=$$?; "
commands + "); G=$$?; "
# Remove the final output if any postbuild failed.
"((exit $$G) || rm -rf %s) " % output
+ "&& exit $$G)"
"((exit $$G) || rm -rf %s) " % output + "&& exit $$G)"
)
if is_command_start:
return "(" + command_string + " && "
@ -1815,10 +1810,7 @@ class NinjaWriter:
"executable": default_variables["EXECUTABLE_SUFFIX"],
}
extension = spec.get("product_extension")
if extension:
extension = "." + extension
else:
extension = DEFAULT_EXTENSION.get(type, "")
extension = "." + extension if extension else DEFAULT_EXTENSION.get(type, "")
if "product_name" in spec:
# If we were given an explicit name, use that.
@ -1953,7 +1945,8 @@ class NinjaWriter:
)
else:
rspfile_content = gyp.msvs_emulation.EncodeRspFileList(
args, win_shell_flags.quote)
args, win_shell_flags.quote
)
command = (
"%s gyp-win-tool action-wrapper $arch " % sys.executable
+ rspfile
@ -1999,7 +1992,7 @@ def CalculateVariables(default_variables, params):
# Copy additional generator configuration data from Xcode, which is shared
# by the Mac Ninja generator.
import gyp.generator.xcode as xcode_generator
import gyp.generator.xcode as xcode_generator # noqa: PLC0415
generator_additional_non_configuration_keys = getattr(
xcode_generator, "generator_additional_non_configuration_keys", []
@ -2022,7 +2015,7 @@ def CalculateVariables(default_variables, params):
# Copy additional generator configuration data from VS, which is shared
# by the Windows Ninja generator.
import gyp.generator.msvs as msvs_generator
import gyp.generator.msvs as msvs_generator # noqa: PLC0415
generator_additional_non_configuration_keys = getattr(
msvs_generator, "generator_additional_non_configuration_keys", []
@ -2079,20 +2072,17 @@ def OpenOutput(path, mode="w"):
def CommandWithWrapper(cmd, wrappers, prog):
wrapper = wrappers.get(cmd, "")
if wrapper:
if wrapper := wrappers.get(cmd, ""):
return wrapper + " " + prog
return prog
def GetDefaultConcurrentLinks():
"""Returns a best-guess for a number of concurrent links."""
pool_size = int(os.environ.get("GYP_LINK_CONCURRENCY", 0))
if pool_size:
if pool_size := int(os.environ.get("GYP_LINK_CONCURRENCY") or 0):
return pool_size
if sys.platform in ("win32", "cygwin"):
import ctypes
class MEMORYSTATUSEX(ctypes.Structure):
_fields_ = [
@ -2113,8 +2103,8 @@ def GetDefaultConcurrentLinks():
# VS 2015 uses 20% more working set than VS 2013 and can consume all RAM
# on a 64 GiB machine.
mem_limit = max(1, stat.ullTotalPhys // (5 * (2 ** 30))) # total / 5GiB
hard_cap = max(1, int(os.environ.get("GYP_LINK_CONCURRENCY_MAX", 2 ** 32)))
mem_limit = max(1, stat.ullTotalPhys // (5 * (2**30))) # total / 5GiB
hard_cap = max(1, int(os.environ.get("GYP_LINK_CONCURRENCY_MAX") or 2**32))
return min(mem_limit, hard_cap)
elif sys.platform.startswith("linux"):
if os.path.exists("/proc/meminfo"):
@ -2125,14 +2115,14 @@ def GetDefaultConcurrentLinks():
if not match:
continue
# Allow 8Gb per link on Linux because Gold is quite memory hungry
return max(1, int(match.group(1)) // (8 * (2 ** 20)))
return max(1, int(match.group(1)) // (8 * (2**20)))
return 1
elif sys.platform == "darwin":
try:
avail_bytes = int(subprocess.check_output(["sysctl", "-n", "hw.memsize"]))
# A static library debug build of Chromium's unit_tests takes ~2.7GB, so
# 4GB per ld process allows for some more bloat.
return max(1, avail_bytes // (4 * (2 ** 30))) # total / 4GB
return max(1, avail_bytes // (4 * (2**30))) # total / 4GB
except subprocess.CalledProcessError:
return 1
else:
@ -2213,6 +2203,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
options = params["options"]
flavor = gyp.common.GetFlavor(params)
generator_flags = params.get("generator_flags", {})
generate_compile_commands = generator_flags.get("compile_commands", False)
# build_dir: relative path from source root to our output files.
# e.g. "out/Debug"
@ -2308,8 +2299,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
key_prefix = re.sub(r"\.HOST$", ".host", key_prefix)
wrappers[key_prefix] = os.path.join(build_to_root, value)
mac_toolchain_dir = generator_flags.get("mac_toolchain_dir", None)
if mac_toolchain_dir:
if mac_toolchain_dir := generator_flags.get("mac_toolchain_dir", None):
wrappers["LINK"] = "export DEVELOPER_DIR='%s' &&" % mac_toolchain_dir
if flavor == "win":
@ -2349,6 +2339,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja.variable("rc", "rc.exe")
master_ninja.variable("ml_x86", "ml.exe")
master_ninja.variable("ml_x64", "ml64.exe")
master_ninja.variable("ml_arm64", "armasm64.exe")
master_ninja.variable("mt", "mt.exe")
else:
master_ninja.variable("ld", CommandWithWrapper("LINK", wrappers, ld))
@ -2420,8 +2411,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
"cc_s",
description="CC $out",
command=(
"$cc $defines $includes $cflags $cflags_c "
"$cflags_pch_c -c $in -o $out"
"$cc $defines $includes $cflags $cflags_c $cflags_pch_c -c $in -o $out"
),
)
master_ninja.rule(
@ -2532,11 +2522,10 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
"solink",
description="SOLINK $lib",
restat=True,
command=mtime_preserving_solink_base
% {"suffix": "@$link_file_list"}, # noqa: E501
command=mtime_preserving_solink_base % {"suffix": "@$link_file_list"},
rspfile="$link_file_list",
rspfile_content=(
"-Wl,--whole-archive $in $solibs -Wl," "--no-whole-archive $libs"
"-Wl,--whole-archive $in $solibs -Wl,--no-whole-archive $libs"
),
pool="link_pool",
)
@ -2596,9 +2585,9 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
"alink",
description="LIBTOOL-STATIC $out, POSTBUILDS",
command="rm -f $out && "
"./gyp-mac-tool filter-libtool libtool $libtool_flags "
"%s gyp-mac-tool filter-libtool libtool $libtool_flags "
"-static -o $out $in"
"$postbuilds",
"$postbuilds" % sys.executable,
)
master_ninja.rule(
"lipo",
@ -2685,7 +2674,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja.rule(
"link",
description="LINK $out, POSTBUILDS",
command=("$ld $ldflags -o $out " "$in $solibs $libs$postbuilds"),
command=("$ld $ldflags -o $out $in $solibs $libs$postbuilds"),
pool="link_pool",
)
master_ninja.rule(
@ -2699,41 +2688,44 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja.rule(
"copy_infoplist",
description="COPY INFOPLIST $in",
command="$env ./gyp-mac-tool copy-info-plist $in $out $binary $keys",
command="$env %s gyp-mac-tool copy-info-plist $in $out $binary $keys"
% sys.executable,
)
master_ninja.rule(
"merge_infoplist",
description="MERGE INFOPLISTS $in",
command="$env ./gyp-mac-tool merge-info-plist $out $in",
command="$env %s gyp-mac-tool merge-info-plist $out $in" % sys.executable,
)
master_ninja.rule(
"compile_xcassets",
description="COMPILE XCASSETS $in",
command="$env ./gyp-mac-tool compile-xcassets $keys $in",
command="$env %s gyp-mac-tool compile-xcassets $keys $in" % sys.executable,
)
master_ninja.rule(
"compile_ios_framework_headers",
description="COMPILE HEADER MAPS AND COPY FRAMEWORK HEADERS $in",
command="$env ./gyp-mac-tool compile-ios-framework-header-map $out "
"$framework $in && $env ./gyp-mac-tool "
"copy-ios-framework-headers $framework $copy_headers",
command="$env %(python)s gyp-mac-tool compile-ios-framework-header-map "
"$out $framework $in && $env %(python)s gyp-mac-tool "
"copy-ios-framework-headers $framework $copy_headers"
% {"python": sys.executable},
)
master_ninja.rule(
"mac_tool",
description="MACTOOL $mactool_cmd $in",
command="$env ./gyp-mac-tool $mactool_cmd $in $out $binary",
command="$env %s gyp-mac-tool $mactool_cmd $in $out $binary"
% sys.executable,
)
master_ninja.rule(
"package_framework",
description="PACKAGE FRAMEWORK $out, POSTBUILDS",
command="./gyp-mac-tool package-framework $out $version$postbuilds "
"&& touch $out",
command="%s gyp-mac-tool package-framework $out $version$postbuilds "
"&& touch $out" % sys.executable,
)
master_ninja.rule(
"package_ios_framework",
description="PACKAGE IOS FRAMEWORK $out, POSTBUILDS",
command="./gyp-mac-tool package-ios-framework $out $postbuilds "
"&& touch $out",
command="%s gyp-mac-tool package-ios-framework $out $postbuilds "
"&& touch $out" % sys.executable,
)
if flavor == "win":
master_ninja.rule(
@ -2811,7 +2803,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
build_file, name, toolset
)
qualified_target_for_hash = qualified_target_for_hash.encode("utf-8")
hash_for_rules = hashlib.md5(qualified_target_for_hash).hexdigest()
hash_for_rules = hashlib.sha256(qualified_target_for_hash).hexdigest()
base_path = os.path.dirname(build_file)
obj = "obj"
@ -2881,6 +2873,35 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name
master_ninja_file.close()
if generate_compile_commands:
compile_db = GenerateCompileDBWithNinja(toplevel_build)
compile_db_file = OpenOutput(
os.path.join(toplevel_build, "compile_commands.json")
)
compile_db_file.write(json.dumps(compile_db, indent=2))
compile_db_file.close()
def GenerateCompileDBWithNinja(path, targets=["all"]):
"""Generates a compile database using ninja.
Args:
path: The build directory to generate a compile database for.
targets: Additional targets to pass to ninja.
Returns:
List of the contents of the compile database.
"""
ninja_path = shutil.which("ninja")
if ninja_path is None:
raise Exception("ninja not found in PATH")
json_compile_db = subprocess.check_output(
[ninja_path, "-C", path]
+ targets
+ ["-t", "compdb", "cc", "cxx", "objc", "objcxx"]
)
return json.loads(json_compile_db)
def PerformBuild(data, configurations, params):
options = params["options"]

View file

@ -4,32 +4,43 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the ninja.py file. """
"""Unit tests for the ninja.py file."""
import sys
import unittest
from pathlib import Path
import gyp.generator.ninja as ninja
from gyp.generator import ninja
from gyp.MSVSVersion import SelectVisualStudioVersion
def _has_visual_studio():
"""Check if Visual Studio can be detected by gyp's registry-based detection."""
if not sys.platform.startswith("win"):
return False
try:
SelectVisualStudioVersion("auto", allow_fallback=False)
return True
except ValueError:
return False
class TestPrefixesAndSuffixes(unittest.TestCase):
@unittest.skipUnless(
_has_visual_studio(),
"requires Windows with a Visual Studio installation detected via the registry",
)
def test_BinaryNamesWindows(self):
# These cannot run on non-Windows as they require a VS installation to
# correctly handle variable expansion.
if sys.platform.startswith("win"):
writer = ninja.NinjaWriter(
"foo", "wee", ".", ".", "build.ninja", ".", "build.ninja", "win"
)
spec = {"target_name": "wee"}
self.assertTrue(
writer.ComputeOutputFileName(spec, "executable").endswith(".exe")
)
self.assertTrue(
writer.ComputeOutputFileName(spec, "shared_library").endswith(".dll")
)
self.assertTrue(
writer.ComputeOutputFileName(spec, "static_library").endswith(".lib")
)
writer = ninja.NinjaWriter(
"foo", "wee", ".", ".", "build.ninja", ".", "build.ninja", "win"
)
spec = {"target_name": "wee"}
for key, ext in {
"executable": ".exe",
"shared_library": ".dll",
"static_library": ".lib",
}.items():
self.assertTrue(writer.ComputeOutputFileName(spec, key).endswith(ext))
def test_BinaryNamesLinux(self):
writer = ninja.NinjaWriter(
@ -50,6 +61,17 @@ class TestPrefixesAndSuffixes(unittest.TestCase):
writer.ComputeOutputFileName(spec, "static_library").endswith(".a")
)
def test_GenerateCompileDBWithNinja(self):
build_dir = (
Path(__file__).resolve().parent.parent.parent.parent / "data" / "ninja"
)
compile_db = ninja.GenerateCompileDBWithNinja(build_dir)
assert len(compile_db) == 1
assert compile_db[0]["directory"] == str(build_dir)
assert compile_db[0]["command"] == "cc my.in my.out"
assert compile_db[0]["file"] == "my.in"
assert compile_db[0]["output"] == "my.out"
if __name__ == "__main__":
unittest.main()

View file

@ -3,19 +3,19 @@
# found in the LICENSE file.
import filecmp
import gyp.common
import gyp.xcodeproj_file
import gyp.xcode_ninja
import errno
import filecmp
import os
import sys
import posixpath
import re
import shutil
import subprocess
import sys
import tempfile
import gyp.common
import gyp.xcode_ninja
import gyp.xcodeproj_file
# Project files generated by this module will use _intermediate_var as a
# custom Xcode setting whose value is a DerivedSources-like directory that's
@ -439,7 +439,7 @@ sys.exit(subprocess.call(sys.argv[1:]))" """
# it opens the project file, which will result in unnecessary diffs.
# TODO(mark): This is evil because it relies on internal knowledge of
# PBXProject._other_pbxprojects.
for other_pbxproject in self.project._other_pbxprojects.keys():
for other_pbxproject in self.project._other_pbxprojects:
self.project.AddOrGetProjectReference(other_pbxproject)
self.project.SortRemoteProductReferences()
@ -531,7 +531,7 @@ def AddSourceToTarget(source, type, pbxp, xct):
library_extensions = ["a", "dylib", "framework", "o"]
basename = posixpath.basename(source)
(root, ext) = posixpath.splitext(basename)
(_root, ext) = posixpath.splitext(basename)
if ext:
ext = ext[1:].lower()
@ -564,12 +564,12 @@ _xcode_variable_re = re.compile(r"(\$\((.*?)\))")
def ExpandXcodeVariables(string, expansions):
"""Expands Xcode-style $(VARIABLES) in string per the expansions dict.
In some rare cases, it is appropriate to expand Xcode variables when a
project file is generated. For any substring $(VAR) in string, if VAR is a
key in the expansions dict, $(VAR) will be replaced with expansions[VAR].
Any $(VAR) substring in string for which VAR is not a key in the expansions
dict will remain in the returned string.
"""
In some rare cases, it is appropriate to expand Xcode variables when a
project file is generated. For any substring $(VAR) in string, if VAR is a
key in the expansions dict, $(VAR) will be replaced with expansions[VAR].
Any $(VAR) substring in string for which VAR is not a key in the expansions
dict will remain in the returned string.
"""
matches = _xcode_variable_re.findall(string)
if matches is None:
@ -592,9 +592,9 @@ _xcode_define_re = re.compile(r"([\\\"\' ])")
def EscapeXcodeDefine(s):
"""We must escape the defines that we give to XCode so that it knows not to
split on spaces and to respect backslash and quote literals. However, we
must not quote the define, or Xcode will incorrectly interpret variables
especially $(inherited)."""
split on spaces and to respect backslash and quote literals. However, we
must not quote the define, or Xcode will incorrectly interpret variables
especially $(inherited)."""
return re.sub(_xcode_define_re, r"\\\1", s)
@ -679,9 +679,9 @@ def GenerateOutput(target_list, target_dicts, data, params):
project_attributes["BuildIndependentTargetsInParallel"] = "YES"
if upgrade_check_project_version:
project_attributes["LastUpgradeCheck"] = upgrade_check_project_version
project_attributes[
"LastTestingUpgradeCheck"
] = upgrade_check_project_version
project_attributes["LastTestingUpgradeCheck"] = (
upgrade_check_project_version
)
project_attributes["LastSwiftUpdateCheck"] = upgrade_check_project_version
pbxp.SetProperty("attributes", project_attributes)
@ -696,7 +696,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
xcode_targets = {}
xcode_target_to_target_dict = {}
for qualified_target in target_list:
[build_file, target_name, toolset] = gyp.common.ParseQualifiedTarget(
[build_file, target_name, _toolset] = gyp.common.ParseQualifiedTarget(
qualified_target
)
@ -734,8 +734,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
"loadable_module+xcuitest": "com.apple.product-type.bundle.ui-testing",
"shared_library+bundle": "com.apple.product-type.framework",
"executable+extension+bundle": "com.apple.product-type.app-extension",
"executable+watch+extension+bundle":
"com.apple.product-type.watchkit-extension",
"executable+watch+extension+bundle": "com.apple.product-type.watchkit-extension", # noqa: E501
"executable+watch+bundle": "com.apple.product-type.application.watchapp",
"mac_kernel_extension+bundle": "com.apple.product-type.kernel-extension",
}
@ -780,8 +779,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
type_bundle_key += "+watch+extension+bundle"
elif is_watch_app:
assert is_bundle, (
"ios_watch_app flag requires mac_bundle "
"(target %s)" % target_name
"ios_watch_app flag requires mac_bundle (target %s)" % target_name
)
type_bundle_key += "+watch+bundle"
elif is_bundle:
@ -793,7 +791,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
except KeyError as e:
gyp.common.ExceptionAppend(
e,
"-- unknown product type while " "writing target %s" % target_name,
"-- unknown product type while writing target %s" % target_name,
)
raise
else:
@ -959,7 +957,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
# would-be additional inputs are newer than the output. Modifying
# the source tree - even just modification times - feels dirty.
# 6564240 Xcode "custom script" build rules always dump all environment
# variables. This is a low-prioroty problem and is not a
# variables. This is a low-priority problem and is not a
# show-stopper.
rules_by_ext = {}
for rule in spec_rules:
@ -1103,7 +1101,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
eol = " \\"
makefile.write(f" {concrete_output}{eol}\n")
for (rule_source, concrete_outputs, message, action) in zip(
for rule_source, concrete_outputs, message, action in zip(
rule["rule_sources"],
concrete_outputs_by_rule_source,
messages,
@ -1118,10 +1116,7 @@ def GenerateOutput(target_list, target_dicts, data, params):
for concrete_output_index, concrete_output in enumerate(
concrete_outputs
):
if concrete_output_index == 0:
bol = ""
else:
bol = " "
bol = "" if concrete_output_index == 0 else " "
makefile.write(f"{bol}{concrete_output} \\\n")
concrete_output_dir = posixpath.dirname(concrete_output)
@ -1220,7 +1215,7 @@ exit 1
# Add "sources".
for source in spec.get("sources", []):
(source_root, source_extension) = posixpath.splitext(source)
(_source_root, source_extension) = posixpath.splitext(source)
if source_extension[1:] not in rules_by_ext:
# AddSourceToTarget will add the file to a root group if it's not
# already there.
@ -1232,7 +1227,7 @@ exit 1
# it's a bundle of any type.
if is_bundle:
for resource in tgt_mac_bundle_resources:
(resource_root, resource_extension) = posixpath.splitext(resource)
(_resource_root, resource_extension) = posixpath.splitext(resource)
if resource_extension[1:] not in rules_by_ext:
AddResourceToTarget(resource, pbxp, xct)
else:

View file

@ -4,11 +4,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Unit tests for the xcode.py file. """
"""Unit tests for the xcode.py file."""
import gyp.generator.xcode as xcode
import unittest
import sys
import unittest
from gyp.generator import xcode
class TestEscapeXcodeDefine(unittest.TestCase):

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,10 @@
"""Unit tests for the input.py file."""
import gyp.input
import unittest
import gyp.input
class TestFindCycles(unittest.TestCase):
def setUp(self):

View file

@ -8,7 +8,6 @@
These functions are executed via gyp-mac-tool when using the Makefile generator.
"""
import fcntl
import fnmatch
import glob
@ -25,14 +24,13 @@ import tempfile
def main(args):
executor = MacTool()
exit_code = executor.Dispatch(args)
if exit_code is not None:
if (exit_code := executor.Dispatch(args)) is not None:
sys.exit(exit_code)
class MacTool:
"""This class performs all the Mac tooling steps. The methods can either be
executed directly, or dispatched from an argument list."""
executed directly, or dispatched from an argument list."""
def Dispatch(self, args):
"""Dispatches a string command to a method."""
@ -48,7 +46,7 @@ class MacTool:
def ExecCopyBundleResource(self, source, dest, convert_to_binary):
"""Copies a resource file to the bundle/Resources directory, performing any
necessary compilation on each resource."""
necessary compilation on each resource."""
convert_to_binary = convert_to_binary == "True"
extension = os.path.splitext(source)[1].lower()
if os.path.isdir(source):
@ -59,9 +57,7 @@ class MacTool:
if os.path.exists(dest):
shutil.rmtree(dest)
shutil.copytree(source, dest)
elif extension == ".xib":
return self._CopyXIBFile(source, dest)
elif extension == ".storyboard":
elif extension in {".xib", ".storyboard"}:
return self._CopyXIBFile(source, dest)
elif extension == ".strings" and not convert_to_binary:
self._CopyStringsFile(source, dest)
@ -70,7 +66,7 @@ class MacTool:
os.unlink(dest)
shutil.copy(source, dest)
if convert_to_binary and extension in (".plist", ".strings"):
if convert_to_binary and extension in {".plist", ".strings"}:
self._ConvertToBinary(dest)
def _CopyXIBFile(self, source, dest):
@ -144,7 +140,7 @@ class MacTool:
# CFPropertyListCreateFromXMLData(): Old-style plist parser: missing
# semicolon in dictionary.
# on invalid files. Do the same kind of validation.
import CoreFoundation
import CoreFoundation # noqa: PLC0415
with open(source, "rb") as in_file:
s = in_file.read()
@ -158,17 +154,15 @@ class MacTool:
def _DetectInputEncoding(self, file_name):
"""Reads the first few bytes from file_name and tries to guess the text
encoding. Returns None as a guess if it can't detect it."""
encoding. Returns None as a guess if it can't detect it."""
with open(file_name, "rb") as fp:
try:
header = fp.read(3)
except Exception:
return None
if header.startswith(b"\xFE\xFF"):
if header.startswith((b"\xfe\xff", b"\xff\xfe")):
return "UTF-16"
elif header.startswith(b"\xFF\xFE"):
return "UTF-16"
elif header.startswith(b"\xEF\xBB\xBF"):
elif header.startswith(b"\xef\xbb\xbf"):
return "UTF-8"
else:
return None
@ -259,9 +253,9 @@ class MacTool:
def ExecFilterLibtool(self, *cmd_list):
"""Calls libtool and filters out '/path/to/libtool: file: foo.o has no
symbols'."""
symbols'."""
libtool_re = re.compile(
r"^.*libtool: (?:for architecture: \S* )?" r"file: .* has no symbols$"
r"^.*libtool: (?:for architecture: \S* )?file: .* has no symbols$"
)
libtool_re5 = re.compile(
r"^.*libtool: warning for library: "
@ -308,7 +302,7 @@ class MacTool:
def ExecPackageFramework(self, framework, version):
"""Takes a path to Something.framework and the Current version of that and
sets up all the symlinks."""
sets up all the symlinks."""
# Find the name of the binary based on the part before the ".framework".
binary = os.path.basename(framework).split(".")[0]
@ -337,7 +331,7 @@ class MacTool:
def _Relink(self, dest, link):
"""Creates a symlink to |dest| named |link|. If |link| already exists,
it is overwritten."""
it is overwritten."""
if os.path.lexists(link):
os.remove(link)
os.symlink(dest, link)
@ -362,14 +356,14 @@ class MacTool:
def ExecCompileXcassets(self, keys, *inputs):
"""Compiles multiple .xcassets files into a single .car file.
This invokes 'actool' to compile all the inputs .xcassets files. The
|keys| arguments is a json-encoded dictionary of extra arguments to
pass to 'actool' when the asset catalogs contains an application icon
or a launch image.
This invokes 'actool' to compile all the inputs .xcassets files. The
|keys| arguments is a json-encoded dictionary of extra arguments to
pass to 'actool' when the asset catalogs contains an application icon
or a launch image.
Note that 'actool' does not create the Assets.car file if the asset
catalogs does not contains imageset.
"""
Note that 'actool' does not create the Assets.car file if the asset
catalogs does not contains imageset.
"""
command_line = [
"xcrun",
"actool",
@ -442,13 +436,13 @@ class MacTool:
def ExecCodeSignBundle(self, key, entitlements, provisioning, path, preserve):
"""Code sign a bundle.
This function tries to code sign an iOS bundle, following the same
algorithm as Xcode:
1. pick the provisioning profile that best match the bundle identifier,
and copy it into the bundle as embedded.mobileprovision,
2. copy Entitlements.plist from user or SDK next to the bundle,
3. code sign the bundle.
"""
This function tries to code sign an iOS bundle, following the same
algorithm as Xcode:
1. pick the provisioning profile that best match the bundle identifier,
and copy it into the bundle as embedded.mobileprovision,
2. copy Entitlements.plist from user or SDK next to the bundle,
3. code sign the bundle.
"""
substitutions, overrides = self._InstallProvisioningProfile(
provisioning, self._GetCFBundleIdentifier()
)
@ -467,16 +461,16 @@ class MacTool:
def _InstallProvisioningProfile(self, profile, bundle_identifier):
"""Installs embedded.mobileprovision into the bundle.
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Returns:
A tuple containing two dictionary: variables substitutions and values
to overrides when generating the entitlements file.
"""
Returns:
A tuple containing two dictionary: variables substitutions and values
to overrides when generating the entitlements file.
"""
source_path, provisioning_data, team_id = self._FindProvisioningProfile(
profile, bundle_identifier
)
@ -492,24 +486,24 @@ class MacTool:
def _FindProvisioningProfile(self, profile, bundle_identifier):
"""Finds the .mobileprovision file to use for signing the bundle.
Checks all the installed provisioning profiles (or if the user specified
the PROVISIONING_PROFILE variable, only consult it) and select the most
specific that correspond to the bundle identifier.
Checks all the installed provisioning profiles (or if the user specified
the PROVISIONING_PROFILE variable, only consult it) and select the most
specific that correspond to the bundle identifier.
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Args:
profile: string, optional, short name of the .mobileprovision file
to use, if empty or the file is missing, the best file installed
will be used
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
Returns:
A tuple of the path to the selected provisioning profile, the data of
the embedded plist in the provisioning profile and the team identifier
to use for code signing.
Returns:
A tuple of the path to the selected provisioning profile, the data of
the embedded plist in the provisioning profile and the team identifier
to use for code signing.
Raises:
SystemExit: if no .mobileprovision can be used to sign the bundle.
"""
Raises:
SystemExit: if no .mobileprovision can be used to sign the bundle.
"""
profiles_dir = os.path.join(
os.environ["HOME"], "Library", "MobileDevice", "Provisioning Profiles"
)
@ -551,18 +545,18 @@ class MacTool:
# If the user has multiple provisioning profiles installed that can be
# used for ${bundle_identifier}, pick the most specific one (ie. the
# provisioning profile whose pattern is the longest).
selected_key = max(valid_provisioning_profiles, key=lambda v: len(v))
selected_key = max(valid_provisioning_profiles, key=len)
return valid_provisioning_profiles[selected_key]
def _LoadProvisioningProfile(self, profile_path):
"""Extracts the plist embedded in a provisioning profile.
Args:
profile_path: string, path to the .mobileprovision file
Args:
profile_path: string, path to the .mobileprovision file
Returns:
Content of the plist embedded in the provisioning profile as a dictionary.
"""
Returns:
Content of the plist embedded in the provisioning profile as a dictionary.
"""
with tempfile.NamedTemporaryFile() as temp:
subprocess.check_call(
["security", "cms", "-D", "-i", profile_path, "-o", temp.name]
@ -585,16 +579,16 @@ class MacTool:
def _LoadPlistMaybeBinary(self, plist_path):
"""Loads into a memory a plist possibly encoded in binary format.
This is a wrapper around plistlib.readPlist that tries to convert the
plist to the XML format if it can't be parsed (assuming that it is in
the binary format).
This is a wrapper around plistlib.readPlist that tries to convert the
plist to the XML format if it can't be parsed (assuming that it is in
the binary format).
Args:
plist_path: string, path to a plist file, in XML or binary format
Args:
plist_path: string, path to a plist file, in XML or binary format
Returns:
Content of the plist as a dictionary.
"""
Returns:
Content of the plist as a dictionary.
"""
try:
# First, try to read the file using plistlib that only supports XML,
# and if an exception is raised, convert a temporary copy to XML and
@ -610,13 +604,13 @@ class MacTool:
def _GetSubstitutions(self, bundle_identifier, app_identifier_prefix):
"""Constructs a dictionary of variable substitutions for Entitlements.plist.
Args:
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
app_identifier_prefix: string, value for AppIdentifierPrefix
Args:
bundle_identifier: string, value of CFBundleIdentifier from Info.plist
app_identifier_prefix: string, value for AppIdentifierPrefix
Returns:
Dictionary of substitutions to apply when generating Entitlements.plist.
"""
Returns:
Dictionary of substitutions to apply when generating Entitlements.plist.
"""
return {
"CFBundleIdentifier": bundle_identifier,
"AppIdentifierPrefix": app_identifier_prefix,
@ -625,9 +619,9 @@ class MacTool:
def _GetCFBundleIdentifier(self):
"""Extracts CFBundleIdentifier value from Info.plist in the bundle.
Returns:
Value of CFBundleIdentifier in the Info.plist located in the bundle.
"""
Returns:
Value of CFBundleIdentifier in the Info.plist located in the bundle.
"""
info_plist_path = os.path.join(
os.environ["TARGET_BUILD_DIR"], os.environ["INFOPLIST_PATH"]
)
@ -637,19 +631,19 @@ class MacTool:
def _InstallEntitlements(self, entitlements, substitutions, overrides):
"""Generates and install the ${BundleName}.xcent entitlements file.
Expands variables "$(variable)" pattern in the source entitlements file,
add extra entitlements defined in the .mobileprovision file and the copy
the generated plist to "${BundlePath}.xcent".
Expands variables "$(variable)" pattern in the source entitlements file,
add extra entitlements defined in the .mobileprovision file and the copy
the generated plist to "${BundlePath}.xcent".
Args:
entitlements: string, optional, path to the Entitlements.plist template
to use, defaults to "${SDKROOT}/Entitlements.plist"
substitutions: dictionary, variable substitutions
overrides: dictionary, values to add to the entitlements
Args:
entitlements: string, optional, path to the Entitlements.plist template
to use, defaults to "${SDKROOT}/Entitlements.plist"
substitutions: dictionary, variable substitutions
overrides: dictionary, values to add to the entitlements
Returns:
Path to the generated entitlements file.
"""
Returns:
Path to the generated entitlements file.
"""
source_path = entitlements
target_path = os.path.join(
os.environ["BUILT_PRODUCTS_DIR"], os.environ["PRODUCT_NAME"] + ".xcent"
@ -669,15 +663,15 @@ class MacTool:
def _ExpandVariables(self, data, substitutions):
"""Expands variables "$(variable)" in data.
Args:
data: object, can be either string, list or dictionary
substitutions: dictionary, variable substitutions to perform
Args:
data: object, can be either string, list or dictionary
substitutions: dictionary, variable substitutions to perform
Returns:
Copy of data where each references to "$(variable)" has been replaced
by the corresponding value found in substitutions, or left intact if
the key was not found.
"""
Returns:
Copy of data where each references to "$(variable)" has been replaced
by the corresponding value found in substitutions, or left intact if
the key was not found.
"""
if isinstance(data, str):
for key, value in substitutions.items():
data = data.replace("$(%s)" % key, value)
@ -696,15 +690,15 @@ def NextGreaterPowerOf2(x):
def WriteHmap(output_name, filelist):
"""Generates a header map based on |filelist|.
Per Mark Mentovai:
A header map is structured essentially as a hash table, keyed by names used
in #includes, and providing pathnames to the actual files.
Per Mark Mentovai:
A header map is structured essentially as a hash table, keyed by names used
in #includes, and providing pathnames to the actual files.
The implementation below and the comment above comes from inspecting:
http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt
while also looking at the implementation in clang in:
https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp
"""
The implementation below and the comment above comes from inspecting:
http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt
while also looking at the implementation in clang in:
https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp
"""
magic = 1751998832
version = 1
_reserved = 0

View file

@ -7,15 +7,15 @@ This module helps emulate Visual Studio 2008 behavior on top of other
build systems, primarily ninja.
"""
import collections
import os
import re
import subprocess
import sys
from collections import namedtuple
from gyp.common import OrderedSet
import gyp.MSVSUtil
import gyp.MSVSVersion
from gyp.common import OrderedSet
windows_quoter_regex = re.compile(r'(\\*)"')
@ -74,8 +74,7 @@ def EncodeRspFileList(args, quote_cmd):
program = call + " " + os.path.normpath(program)
else:
program = os.path.normpath(args[0])
return (program + " "
+ " ".join(QuoteForRspFile(arg, quote_cmd) for arg in args[1:]))
return program + " " + " ".join(QuoteForRspFile(arg, quote_cmd) for arg in args[1:])
def _GenericRetrieve(root, default, path):
@ -93,7 +92,7 @@ def _AddPrefix(element, prefix):
if element is None:
return element
# Note, not Iterable because we don't want to handle strings like that.
if isinstance(element, list) or isinstance(element, tuple):
if isinstance(element, (list, tuple)):
return [prefix + e for e in element]
else:
return prefix + element
@ -105,7 +104,7 @@ def _DoRemapping(element, map):
if map is not None and element is not None:
if not callable(map):
map = map.get # Assume it's a dict, otherwise a callable to do the remap.
if isinstance(element, list) or isinstance(element, tuple):
if isinstance(element, (list, tuple)):
element = filter(None, [map(elem) for elem in element])
else:
element = map(element)
@ -117,7 +116,7 @@ def _AppendOrReturn(append, element):
then add |element| to it, adding each item in |element| if it's a list or
tuple."""
if append is not None and element is not None:
if isinstance(element, list) or isinstance(element, tuple):
if isinstance(element, (list, tuple)):
append.extend(element)
else:
append.append(element)
@ -183,7 +182,7 @@ def ExtractSharedMSVSSystemIncludes(configs, generator_flags):
expanded_system_includes = OrderedSet(
[ExpandMacros(include, env) for include in all_system_includes]
)
if any(["$" in include for include in expanded_system_includes]):
if any("$" in include for include in expanded_system_includes):
# Some path relies on target-specific variables, bail.
return None
@ -247,18 +246,13 @@ class MsvsSettings:
the target type.
"""
ext = self.spec.get("product_extension", None)
if ext:
return ext
return gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec["type"], "")
return ext or gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec["type"], "")
def GetVSMacroEnv(self, base_to_build=None, config=None):
"""Get a dict of variables mapping internal VS macro names to their gyp
equivalents."""
target_arch = self.GetArch(config)
if target_arch == "x86":
target_platform = "Win32"
else:
target_platform = target_arch
target_platform = "Win32" if target_arch == "x86" else target_arch
target_name = self.spec.get("product_prefix", "") + self.spec.get(
"product_name", self.spec["target_name"]
)
@ -628,8 +622,7 @@ class MsvsSettings:
def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path):
""".def files get implicitly converted to a ModuleDefinitionFile for the
linker in the VS generator. Emulate that behaviour here."""
def_file = self.GetDefFile(gyp_to_build_path)
if def_file:
if def_file := self.GetDefFile(gyp_to_build_path):
ldflags.append('/DEF:"%s"' % def_file)
def GetPGDName(self, config, expand_special):
@ -677,14 +670,11 @@ class MsvsSettings:
)
ld("DelayLoadDLLs", prefix="/DELAYLOAD:")
ld("TreatLinkerWarningAsErrors", prefix="/WX", map={"true": "", "false": ":NO"})
out = self.GetOutputName(config, expand_special)
if out:
if out := self.GetOutputName(config, expand_special):
ldflags.append("/OUT:" + out)
pdb = self.GetPDBName(config, expand_special, output_name + ".pdb")
if pdb:
if pdb := self.GetPDBName(config, expand_special, output_name + ".pdb"):
ldflags.append("/PDB:" + pdb)
pgd = self.GetPGDName(config, expand_special)
if pgd:
if pgd := self.GetPGDName(config, expand_special):
ldflags.append("/PGD:" + pgd)
map_file = self.GetMapFileName(config, expand_special)
ld("GenerateMapFile", map={"true": "/MAP:" + map_file if map_file else "/MAP"})
@ -738,10 +728,7 @@ class MsvsSettings:
# TODO(scottmg): This should sort of be somewhere else (not really a flag).
ld("AdditionalDependencies", prefix="")
if self.GetArch(config) == "x86":
safeseh_default = "true"
else:
safeseh_default = None
safeseh_default = "true" if self.GetArch(config) == "x86" else None
ld(
"ImageHasSafeExceptionHandlers",
map={"false": ":NO", "true": ""},
@ -836,17 +823,15 @@ class MsvsSettings:
("VCLinkerTool", "UACUIAccess"), config, default="false"
)
inner = """
level = execution_level_map[execution_level]
inner = f"""
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='{}' uiAccess='{}' />
<requestedExecutionLevel level='{level}' uiAccess='{ui_access}' />
</requestedPrivileges>
</security>
</trustInfo>""".format(
execution_level_map[execution_level],
ui_access,
)
</trustInfo>"""
else:
inner = ""
@ -941,34 +926,34 @@ class MsvsSettings:
)
return cmd
RuleShellFlags = collections.namedtuple("RuleShellFlags", ["cygwin", "quote"])
RuleShellFlags = namedtuple("RuleShellFlags", ["cygwin", "quote"]) # noqa: PYI024
def GetRuleShellFlags(self, rule):
"""Return RuleShellFlags about how the given rule should be run. This
includes whether it should run under cygwin (msvs_cygwin_shell), and
whether the commands should be quoted (msvs_quote_cmd)."""
# If the variable is unset, or set to 1 we use cygwin
cygwin = int(rule.get("msvs_cygwin_shell",
self.spec.get("msvs_cygwin_shell", 1))) != 0
cygwin = (
int(rule.get("msvs_cygwin_shell", self.spec.get("msvs_cygwin_shell", 1)))
!= 0
)
# Default to quoting. There's only a few special instances where the
# target command uses non-standard command line parsing and handle quotes
# and quote escaping differently.
quote_cmd = int(rule.get("msvs_quote_cmd", 1))
assert quote_cmd != 0 or cygwin != 1, \
"msvs_quote_cmd=0 only applicable for msvs_cygwin_shell=0"
assert quote_cmd != 0 or cygwin != 1, (
"msvs_quote_cmd=0 only applicable for msvs_cygwin_shell=0"
)
return MsvsSettings.RuleShellFlags(cygwin, quote_cmd)
def _HasExplicitRuleForExtension(self, spec, extension):
"""Determine if there's an explicit rule for a particular extension."""
for rule in spec.get("rules", []):
if rule["extension"] == extension:
return True
return False
return any(rule["extension"] == extension for rule in spec.get("rules", []))
def _HasExplicitIdlActions(self, spec):
"""Determine if an action should not run midl for .idl files."""
return any(
[action.get("explicit_idl_action", 0) for action in spec.get("actions", [])]
action.get("explicit_idl_action", 0) for action in spec.get("actions", [])
)
def HasExplicitIdlRulesOrActions(self, spec):
@ -1146,8 +1131,7 @@ def _ExtractImportantEnvironment(output_of_set):
for required in ("SYSTEMROOT", "TEMP", "TMP"):
if required not in env:
raise Exception(
'Environment variable "%s" '
"required to be set to valid path" % required
'Environment variable "%s" required to be set to valid path' % required
)
return env
@ -1190,7 +1174,7 @@ def GenerateEnvironmentFiles(
meet your requirement (e.g. for custom toolchains), you can pass
"-G ninja_use_custom_environment_files" to the gyp to suppress file
generation and use custom environment files prepared by yourself."""
archs = ("x86", "x64")
archs = ("x86", "x64", "arm64")
if generator_flags.get("ninja_use_custom_environment_files", 0):
cl_paths = {}
for arch in archs:

View file

@ -17,15 +17,15 @@ __all__ = ["Error", "deepcopy"]
def deepcopy(x):
"""Deep copy operation on gyp objects such as strings, ints, dicts
and lists. More than twice as fast as copy.deepcopy but much less
generic."""
and lists. More than twice as fast as copy.deepcopy but much less
generic."""
try:
return _deepcopy_dispatch[type(x)](x)
except KeyError:
raise Error(
"Unsupported type %s for deepcopy. Use copy.deepcopy "
+ "or expand simple_copy support." % type(x)
f"Unsupported type {type(x)} for deepcopy. Use copy.deepcopy "
+ "or expand simple_copy support."
)

View file

@ -9,13 +9,12 @@
These functions are executed via gyp-win-tool when using the ninja generator.
"""
import os
import re
import shutil
import subprocess
import stat
import string
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
@ -27,18 +26,17 @@ _LINK_EXE_OUT_ARG = re.compile("/OUT:(?P<out>.+)$", re.IGNORECASE)
def main(args):
executor = WinTool()
exit_code = executor.Dispatch(args)
if exit_code is not None:
if (exit_code := executor.Dispatch(args)) is not None:
sys.exit(exit_code)
class WinTool:
"""This class performs all the Windows tooling steps. The methods can either
be executed directly, or dispatched from an argument list."""
be executed directly, or dispatched from an argument list."""
def _UseSeparateMspdbsrv(self, env, args):
"""Allows to use a unique instance of mspdbsrv.exe per linker instead of a
shared one."""
shared one."""
if len(args) < 1:
raise Exception("Not enough arguments")
@ -115,9 +113,9 @@ class WinTool:
def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
"""Filter diagnostic output from link that looks like:
' Creating library ui.dll.lib and object ui.dll.exp'
This happens when there are exports from the dll or exe.
"""
' Creating library ui.dll.lib and object ui.dll.exp'
This happens when there are exports from the dll or exe.
"""
env = self._GetEnv(arch)
if use_separate_mspdbsrv == "True":
self._UseSeparateMspdbsrv(env, args)
@ -159,10 +157,10 @@ class WinTool:
mt,
rc,
intermediate_manifest,
*manifests
*manifests,
):
"""A wrapper for handling creating a manifest resource and then executing
a link command."""
a link command."""
# The 'normal' way to do manifests is to have link generate a manifest
# based on gathering dependencies from the object files, then merge that
# manifest with other manifests supplied as sources, convert the merged
@ -219,11 +217,10 @@ class WinTool:
our_manifest = "%(out)s.manifest" % variables
# Load and normalize the manifests. mt.exe sometimes removes whitespace,
# and sometimes doesn't unfortunately.
with open(our_manifest) as our_f:
with open(assert_manifest) as assert_f:
translator = str.maketrans('', '', string.whitespace)
our_data = our_f.read().translate(translator)
assert_data = assert_f.read().translate(translator)
with open(our_manifest) as our_f, open(assert_manifest) as assert_f:
translator = str.maketrans("", "", string.whitespace)
our_data = our_f.read().translate(translator)
assert_data = assert_f.read().translate(translator)
if our_data != assert_data:
os.unlink(out)
@ -247,8 +244,8 @@ class WinTool:
def ExecManifestWrapper(self, arch, *args):
"""Run manifest tool with environment set. Strip out undesirable warning
(some XML blocks are recognized by the OS loader, but not the manifest
tool)."""
(some XML blocks are recognized by the OS loader, but not the manifest
tool)."""
env = self._GetEnv(arch)
popen = subprocess.Popen(
args, shell=True, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
@ -261,8 +258,8 @@ class WinTool:
def ExecManifestToRc(self, arch, *args):
"""Creates a resource file pointing a SxS assembly manifest.
|args| is tuple containing path to resource file, path to manifest file
and resource name which can be "1" (for executables) or "2" (for DLLs)."""
|args| is tuple containing path to resource file, path to manifest file
and resource name which can be "1" (for executables) or "2" (for DLLs)."""
manifest_path, resource_path, resource_name = args
with open(resource_path, "w") as output:
output.write(
@ -272,8 +269,8 @@ class WinTool:
def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl, *flags):
"""Filter noisy filenames output from MIDL compile step that isn't
quietable via command line flags.
"""
quietable via command line flags.
"""
args = (
["midl", "/nologo"]
+ list(flags)
@ -329,7 +326,7 @@ class WinTool:
def ExecRcWrapper(self, arch, *args):
"""Filter logo banner from invocations of rc.exe. Older versions of RC
don't support the /nologo flag."""
don't support the /nologo flag."""
env = self._GetEnv(arch)
popen = subprocess.Popen(
args, shell=True, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
@ -346,7 +343,7 @@ class WinTool:
def ExecActionWrapper(self, arch, rspfile, *dir):
"""Runs an action command line from a response file using the environment
for |arch|. If |dir| is supplied, use that as the working directory."""
for |arch|. If |dir| is supplied, use that as the working directory."""
env = self._GetEnv(arch)
# TODO(scottmg): This is a temporary hack to get some specific variables
# through to actions that are set after gyp-time. http://crbug.com/333738.
@ -359,7 +356,7 @@ class WinTool:
def ExecClCompile(self, project_dir, selected_files):
"""Executed by msvs-ninja projects when the 'ClCompile' target is used to
build selected C/C++ files."""
build selected C/C++ files."""
project_dir = os.path.relpath(project_dir, BASE_DIR)
selected_files = selected_files.split(";")
ninja_targets = [

View file

@ -7,15 +7,15 @@ This module contains classes that help to emulate xcodebuild behavior on top of
other build systems, such as make and ninja.
"""
import copy
import gyp.common
import os
import os.path
import re
import shlex
import subprocess
import sys
import gyp.common
from gyp.common import GypError
# Populated lazily by XcodeVersion, for efficiency, and to fix an issue when
@ -30,7 +30,7 @@ XCODE_ARCHS_DEFAULT_CACHE = None
def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
"""Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable,
and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
mapping = {"$(ARCHS_STANDARD)": archs}
if archs_including_64_bit:
mapping["$(ARCHS_STANDARD_INCLUDING_64_BIT)"] = archs_including_64_bit
@ -39,10 +39,10 @@ def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
class XcodeArchsDefault:
"""A class to resolve ARCHS variable from xcode_settings, resolving Xcode
macros and implementing filtering by VALID_ARCHS. The expansion of macros
depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
on the version of Xcode.
"""
macros and implementing filtering by VALID_ARCHS. The expansion of macros
depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
on the version of Xcode.
"""
# Match variable like $(ARCHS_STANDARD).
variable_pattern = re.compile(r"\$\([a-zA-Z_][a-zA-Z0-9_]*\)$")
@ -81,8 +81,8 @@ class XcodeArchsDefault:
def ActiveArchs(self, archs, valid_archs, sdkroot):
"""Expands variables references in ARCHS, and filter by VALID_ARCHS if it
is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
values present in VALID_ARCHS are kept)."""
is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
values present in VALID_ARCHS are kept)."""
expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or "")
if valid_archs:
filtered_archs = []
@ -95,24 +95,24 @@ class XcodeArchsDefault:
def GetXcodeArchsDefault():
"""Returns the |XcodeArchsDefault| object to use to expand ARCHS for the
installed version of Xcode. The default values used by Xcode for ARCHS
and the expansion of the variables depends on the version of Xcode used.
installed version of Xcode. The default values used by Xcode for ARCHS
and the expansion of the variables depends on the version of Xcode used.
For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
$(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
and deprecated with Xcode 5.1.
For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
$(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
and deprecated with Xcode 5.1.
For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
architecture as part of $(ARCHS_STANDARD) and default to only building it.
For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
architecture as part of $(ARCHS_STANDARD) and default to only building it.
For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
are also part of $(ARCHS_STANDARD).
For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
are also part of $(ARCHS_STANDARD).
All these rules are coded in the construction of the |XcodeArchsDefault|
object to use depending on the version of Xcode detected. The object is
for performance reason."""
All these rules are coded in the construction of the |XcodeArchsDefault|
object to use depending on the version of Xcode detected. The object is
for performance reason."""
global XCODE_ARCHS_DEFAULT_CACHE
if XCODE_ARCHS_DEFAULT_CACHE:
return XCODE_ARCHS_DEFAULT_CACHE
@ -189,8 +189,8 @@ class XcodeSettings:
def _ConvertConditionalKeys(self, configname):
"""Converts or warns on conditional keys. Xcode supports conditional keys,
such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
with some keys converted while the rest force a warning."""
such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
with some keys converted while the rest force a warning."""
settings = self.xcode_settings[configname]
conditional_keys = [key for key in settings if key.endswith("]")]
for key in conditional_keys:
@ -255,13 +255,13 @@ class XcodeSettings:
def GetFrameworkVersion(self):
"""Returns the framework version of the current target. Only valid for
bundles."""
bundles."""
assert self._IsBundle()
return self.GetPerTargetSetting("FRAMEWORK_VERSION", default="A")
def GetWrapperExtension(self):
"""Returns the bundle extension (.app, .framework, .plugin, etc). Only
valid for bundles."""
valid for bundles."""
assert self._IsBundle()
if self.spec["type"] in ("loadable_module", "shared_library"):
default_wrapper_extension = {
@ -296,13 +296,13 @@ class XcodeSettings:
def GetWrapperName(self):
"""Returns the directory name of the bundle represented by this target.
Only valid for bundles."""
Only valid for bundles."""
assert self._IsBundle()
return self.GetProductName() + self.GetWrapperExtension()
def GetBundleContentsFolderPath(self):
"""Returns the qualified path to the bundle's contents folder. E.g.
Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
if self.isIOS:
return self.GetWrapperName()
assert self._IsBundle()
@ -316,7 +316,7 @@ class XcodeSettings:
def GetBundleResourceFolder(self):
"""Returns the qualified path to the bundle's resource folder. E.g.
Chromium.app/Contents/Resources. Only valid for bundles."""
Chromium.app/Contents/Resources. Only valid for bundles."""
assert self._IsBundle()
if self.isIOS:
return self.GetBundleContentsFolderPath()
@ -324,7 +324,7 @@ class XcodeSettings:
def GetBundleExecutableFolderPath(self):
"""Returns the qualified path to the bundle's executables folder. E.g.
Chromium.app/Contents/MacOS. Only valid for bundles."""
Chromium.app/Contents/MacOS. Only valid for bundles."""
assert self._IsBundle()
if self.spec["type"] in ("shared_library") or self.isIOS:
return self.GetBundleContentsFolderPath()
@ -333,25 +333,25 @@ class XcodeSettings:
def GetBundleJavaFolderPath(self):
"""Returns the qualified path to the bundle's Java resource folder.
E.g. Chromium.app/Contents/Resources/Java. Only valid for bundles."""
E.g. Chromium.app/Contents/Resources/Java. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleResourceFolder(), "Java")
def GetBundleFrameworksFolderPath(self):
"""Returns the qualified path to the bundle's frameworks folder. E.g,
Chromium.app/Contents/Frameworks. Only valid for bundles."""
Chromium.app/Contents/Frameworks. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "Frameworks")
def GetBundleSharedFrameworksFolderPath(self):
"""Returns the qualified path to the bundle's frameworks folder. E.g,
Chromium.app/Contents/SharedFrameworks. Only valid for bundles."""
Chromium.app/Contents/SharedFrameworks. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "SharedFrameworks")
def GetBundleSharedSupportFolderPath(self):
"""Returns the qualified path to the bundle's shared support folder. E.g,
Chromium.app/Contents/SharedSupport. Only valid for bundles."""
Chromium.app/Contents/SharedSupport. Only valid for bundles."""
assert self._IsBundle()
if self.spec["type"] == "shared_library":
return self.GetBundleResourceFolder()
@ -360,19 +360,19 @@ class XcodeSettings:
def GetBundlePlugInsFolderPath(self):
"""Returns the qualified path to the bundle's plugins folder. E.g,
Chromium.app/Contents/PlugIns. Only valid for bundles."""
Chromium.app/Contents/PlugIns. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "PlugIns")
def GetBundleXPCServicesFolderPath(self):
"""Returns the qualified path to the bundle's XPC services folder. E.g,
Chromium.app/Contents/XPCServices. Only valid for bundles."""
Chromium.app/Contents/XPCServices. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(self.GetBundleContentsFolderPath(), "XPCServices")
def GetBundlePlistPath(self):
"""Returns the qualified path to the bundle's plist file. E.g.
Chromium.app/Contents/Info.plist. Only valid for bundles."""
Chromium.app/Contents/Info.plist. Only valid for bundles."""
assert self._IsBundle()
if (
self.spec["type"] in ("executable", "loadable_module")
@ -438,7 +438,7 @@ class XcodeSettings:
def _GetBundleBinaryPath(self):
"""Returns the name of the bundle binary of by this target.
E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
assert self._IsBundle()
return os.path.join(
self.GetBundleExecutableFolderPath(), self.GetExecutableName()
@ -469,19 +469,16 @@ class XcodeSettings:
def _GetStandaloneBinaryPath(self):
"""Returns the name of the non-bundle binary represented by this target.
E.g. hello_world. Only valid for non-bundles."""
E.g. hello_world. Only valid for non-bundles."""
assert not self._IsBundle()
assert self.spec["type"] in (
assert self.spec["type"] in {
"executable",
"shared_library",
"static_library",
"loadable_module",
), ("Unexpected type %s" % self.spec["type"])
}, "Unexpected type %s" % self.spec["type"]
target = self.spec["target_name"]
if self.spec["type"] == "static_library":
if target[:3] == "lib":
target = target[3:]
elif self.spec["type"] in ("loadable_module", "shared_library"):
if self.spec["type"] in {"loadable_module", "shared_library", "static_library"}:
if target[:3] == "lib":
target = target[3:]
@ -492,7 +489,7 @@ class XcodeSettings:
def GetExecutableName(self):
"""Returns the executable name of the bundle represented by this target.
E.g. Chromium."""
E.g. Chromium."""
if self._IsBundle():
return self.spec.get("product_name", self.spec["target_name"])
else:
@ -500,7 +497,7 @@ class XcodeSettings:
def GetExecutablePath(self):
"""Returns the qualified path to the primary executable of the bundle
represented by this target. E.g. Chromium.app/Contents/MacOS/Chromium."""
represented by this target. E.g. Chromium.app/Contents/MacOS/Chromium."""
if self._IsBundle():
return self._GetBundleBinaryPath()
else:
@ -523,7 +520,7 @@ class XcodeSettings:
# most sensible route and should still do the right thing.
try:
return GetStdoutQuiet(["xcrun", "--sdk", sdk, infoitem])
except GypError:
except (GypError, OSError):
pass
def _SdkRoot(self, configname):
@ -570,7 +567,7 @@ class XcodeSettings:
def GetCflags(self, configname, arch=None):
"""Returns flags that need to be added to .c, .cc, .m, and .mm
compilations."""
compilations."""
# This functions (and the similar ones below) do not offer complete
# emulation of all xcode_settings keys. They're implemented on demand.
@ -579,7 +576,8 @@ class XcodeSettings:
sdk_root = self._SdkPath()
if "SDKROOT" in self._Settings() and sdk_root:
cflags.append("-isysroot %s" % sdk_root)
cflags.append("-isysroot")
cflags.append(sdk_root)
if self.header_map_path:
cflags.append("-I%s" % self.header_map_path)
@ -664,7 +662,8 @@ class XcodeSettings:
# TODO: Supporting fat binaries will be annoying.
self._WarnUnimplemented("ARCHS")
archs = ["i386"]
cflags.append("-arch " + archs[0])
cflags.append("-arch")
cflags.append(archs[0])
if archs[0] in ("i386", "x86_64"):
if self._Test("GCC_ENABLE_SSE3_EXTENSIONS", "YES", default="NO"):
@ -685,10 +684,7 @@ class XcodeSettings:
if platform_root:
cflags.append("-F" + platform_root + "/Developer/Library/Frameworks/")
if sdk_root:
framework_root = sdk_root
else:
framework_root = ""
framework_root = sdk_root if sdk_root else ""
config = self.spec["configurations"][self.configname]
framework_dirs = config.get("mac_framework_dirs", [])
for directory in framework_dirs:
@ -866,7 +862,7 @@ class XcodeSettings:
def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path):
"""Checks if ldflag contains a filename and if so remaps it from
gyp-directory-relative to build-directory-relative."""
gyp-directory-relative to build-directory-relative."""
# This list is expanded on demand.
# They get matched as:
# -exported_symbols_list file
@ -898,13 +894,13 @@ class XcodeSettings:
def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
"""Returns flags that need to be passed to the linker.
Args:
configname: The name of the configuration to get ld flags for.
product_dir: The directory where products such static and dynamic
libraries are placed. This is added to the library search path.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
Args:
configname: The name of the configuration to get ld flags for.
product_dir: The directory where products such static and dynamic
libraries are placed. This is added to the library search path.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
self.configname = configname
ldflags = []
@ -927,17 +923,15 @@ class XcodeSettings:
self._AppendPlatformVersionMinFlags(ldflags)
if "SDKROOT" in self._Settings() and self._SdkPath():
ldflags.append("-isysroot " + self._SdkPath())
ldflags.append("-isysroot")
ldflags.append(self._SdkPath())
for library_path in self._Settings().get("LIBRARY_SEARCH_PATHS", []):
ldflags.append("-L" + gyp_to_build_path(library_path))
if "ORDER_FILE" in self._Settings():
ldflags.append(
"-Wl,-order_file "
+ "-Wl,"
+ gyp_to_build_path(self._Settings()["ORDER_FILE"])
)
ldflags.append("-Wl,-order_file")
ldflags.append("-Wl," + gyp_to_build_path(self._Settings()["ORDER_FILE"]))
if not gyp.common.CrossCompileRequested():
if arch is not None:
@ -949,7 +943,9 @@ class XcodeSettings:
# TODO: Supporting fat binaries will be annoying.
self._WarnUnimplemented("ARCHS")
archs = ["i386"]
ldflags.append("-arch " + archs[0])
# Avoid quoting the space between -arch and the arch name
ldflags.append("-arch")
ldflags.append(archs[0])
# Xcode adds the product directory by default.
# Rewrite -L. to -L./ to work around http://www.openradar.me/25313838
@ -957,7 +953,8 @@ class XcodeSettings:
install_name = self.GetInstallName()
if install_name and self.spec["type"] != "loadable_module":
ldflags.append("-install_name " + install_name.replace(" ", r"\ "))
ldflags.append("-install_name")
ldflags.append(install_name.replace(" ", r"\ "))
for rpath in self._Settings().get("LD_RUNPATH_SEARCH_PATHS", []):
ldflags.append("-Wl,-rpath," + rpath)
@ -974,7 +971,8 @@ class XcodeSettings:
platform_root = self._XcodePlatformPath(configname)
if sdk_root and platform_root:
ldflags.append("-F" + platform_root + "/Developer/Library/Frameworks/")
ldflags.append("-framework XCTest")
ldflags.append("-framework")
ldflags.append("XCTest")
is_extension = self._IsIosAppExtension() or self._IsIosWatchKitExtension()
if sdk_root and is_extension:
@ -990,7 +988,8 @@ class XcodeSettings:
+ "/System/Library/PrivateFrameworks/PlugInKit.framework/PlugInKit"
)
else:
ldflags.append("-e _NSExtensionMain")
ldflags.append("-e")
ldflags.append("_NSExtensionMain")
ldflags.append("-fapplication-extension")
self._Appendf(ldflags, "CLANG_CXX_LIBRARY", "-stdlib=%s")
@ -1001,9 +1000,9 @@ class XcodeSettings:
def GetLibtoolflags(self, configname):
"""Returns flags that need to be passed to the static linker.
Args:
configname: The name of the configuration to get ld flags for.
"""
Args:
configname: The name of the configuration to get ld flags for.
"""
self.configname = configname
libtoolflags = []
@ -1016,7 +1015,7 @@ class XcodeSettings:
def GetPerTargetSettings(self):
"""Gets a list of all the per-target settings. This will only fetch keys
whose values are the same across all configurations."""
whose values are the same across all configurations."""
first_pass = True
result = {}
for configname in sorted(self.xcode_settings.keys()):
@ -1039,7 +1038,7 @@ class XcodeSettings:
def GetPerTargetSetting(self, setting, default=None):
"""Tries to get xcode_settings.setting from spec. Assumes that the setting
has the same value in all configurations and throws otherwise."""
has the same value in all configurations and throws otherwise."""
is_first_pass = True
result = None
for configname in sorted(self.xcode_settings.keys()):
@ -1057,15 +1056,14 @@ class XcodeSettings:
def _GetStripPostbuilds(self, configname, output_binary, quiet):
"""Returns a list of shell commands that contain the shell commands
necessary to strip this target's binary. These should be run as postbuilds
before the actual postbuilds run."""
necessary to strip this target's binary. These should be run as postbuilds
before the actual postbuilds run."""
self.configname = configname
result = []
if self._Test("DEPLOYMENT_POSTPROCESSING", "YES", default="NO") and self._Test(
"STRIP_INSTALLED_PRODUCT", "YES", default="NO"
):
default_strip_style = "debugging"
if (
self.spec["type"] == "loadable_module" or self._IsIosAppExtension()
@ -1092,8 +1090,8 @@ class XcodeSettings:
def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet):
"""Returns a list of shell commands that contain the shell commands
necessary to massage this target's debug information. These should be run
as postbuilds before the actual postbuilds run."""
necessary to massage this target's debug information. These should be run
as postbuilds before the actual postbuilds run."""
self.configname = configname
# For static libraries, no dSYMs are created.
@ -1114,7 +1112,7 @@ class XcodeSettings:
def _GetTargetPostbuilds(self, configname, output, output_binary, quiet=False):
"""Returns a list of shell commands that contain the shell commands
to run as postbuilds for this target, before the actual postbuilds."""
to run as postbuilds for this target, before the actual postbuilds."""
# dSYMs need to build before stripping happens.
return self._GetDebugInfoPostbuilds(
configname, output, output_binary, quiet
@ -1122,11 +1120,10 @@ class XcodeSettings:
def _GetIOSPostbuilds(self, configname, output_binary):
"""Return a shell command to codesign the iOS output binary so it can
be deployed to a device. This should be run as the very last step of the
build."""
be deployed to a device. This should be run as the very last step of the
build."""
if not (
self.isIOS
and (self.spec["type"] == "executable" or self._IsXCTest())
(self.isIOS and (self.spec["type"] == "executable" or self._IsXCTest()))
or self.IsIosFramework()
):
return []
@ -1172,8 +1169,9 @@ class XcodeSettings:
# Then re-sign everything with 'preserve=True'
postbuilds.extend(
[
'%s code-sign-bundle "%s" "%s" "%s" "%s" %s'
'%s %s code-sign-bundle "%s" "%s" "%s" "%s" %s'
% (
sys.executable,
os.path.join("${TARGET_BUILD_DIR}", "gyp-mac-tool"),
key,
settings.get("CODE_SIGN_ENTITLEMENTS", ""),
@ -1188,8 +1186,9 @@ class XcodeSettings:
for target in targets:
postbuilds.extend(
[
'%s code-sign-bundle "%s" "%s" "%s" "%s" %s'
'%s %s code-sign-bundle "%s" "%s" "%s" "%s" %s'
% (
sys.executable,
os.path.join("${TARGET_BUILD_DIR}", "gyp-mac-tool"),
key,
settings.get("CODE_SIGN_ENTITLEMENTS", ""),
@ -1202,8 +1201,9 @@ class XcodeSettings:
postbuilds.extend(
[
'%s code-sign-bundle "%s" "%s" "%s" "%s" %s'
'%s %s code-sign-bundle "%s" "%s" "%s" "%s" %s'
% (
sys.executable,
os.path.join("${TARGET_BUILD_DIR}", "gyp-mac-tool"),
key,
settings.get("CODE_SIGN_ENTITLEMENTS", ""),
@ -1237,7 +1237,7 @@ class XcodeSettings:
self, configname, output, output_binary, postbuilds=[], quiet=False
):
"""Returns a list of shell commands that should run before and after
|postbuilds|."""
|postbuilds|."""
assert output_binary is not None
pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
post = self._GetIOSPostbuilds(configname, output_binary)
@ -1248,10 +1248,7 @@ class XcodeSettings:
l_flag = "-framework " + os.path.splitext(os.path.basename(library))[0]
else:
m = self.library_re.match(library)
if m:
l_flag = "-l" + m.group(1)
else:
l_flag = library
l_flag = "-l" + m.group(1) if m else library
sdk_root = self._SdkPath(config_name)
if not sdk_root:
@ -1276,8 +1273,8 @@ class XcodeSettings:
def AdjustLibraries(self, libraries, config_name=None):
"""Transforms entries like 'Cocoa.framework' in libraries into entries like
'-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
"""
'-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
"""
libraries = [self._AdjustLibrary(library, config_name) for library in libraries]
return libraries
@ -1342,20 +1339,19 @@ class XcodeSettings:
def _DefaultSdkRoot(self):
"""Returns the default SDKROOT to use.
Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
project, then the environment variable was empty. Starting with this
version, Xcode uses the name of the newest SDK installed.
"""
Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
project, then the environment variable was empty. Starting with this
version, Xcode uses the name of the newest SDK installed.
"""
xcode_version, _ = XcodeVersion()
if xcode_version < "0500":
return ""
default_sdk_path = self._XcodeSdkPath("")
default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
if default_sdk_root:
if default_sdk_root := XcodeSettings._sdk_root_cache.get(default_sdk_path):
return default_sdk_root
try:
all_sdks = GetStdout(["xcodebuild", "-showsdks"])
except GypError:
except (GypError, OSError):
# If xcodebuild fails, there will be no valid SDKs
return ""
for line in all_sdks.splitlines():
@ -1371,39 +1367,39 @@ class XcodeSettings:
class MacPrefixHeader:
"""A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
This feature consists of several pieces:
* If GCC_PREFIX_HEADER is present, all compilations in that project get an
additional |-include path_to_prefix_header| cflag.
* If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
instead compiled, and all other compilations in the project get an
additional |-include path_to_compiled_header| instead.
+ Compiled prefix headers have the extension gch. There is one gch file for
every language used in the project (c, cc, m, mm), since gch files for
different languages aren't compatible.
+ gch files themselves are built with the target's normal cflags, but they
obviously don't get the |-include| flag. Instead, they need a -x flag that
describes their language.
+ All o files in the target need to depend on the gch file, to make sure
it's built before any o file is built.
This feature consists of several pieces:
* If GCC_PREFIX_HEADER is present, all compilations in that project get an
additional |-include path_to_prefix_header| cflag.
* If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
instead compiled, and all other compilations in the project get an
additional |-include path_to_compiled_header| instead.
+ Compiled prefix headers have the extension gch. There is one gch file for
every language used in the project (c, cc, m, mm), since gch files for
different languages aren't compatible.
+ gch files themselves are built with the target's normal cflags, but they
obviously don't get the |-include| flag. Instead, they need a -x flag that
describes their language.
+ All o files in the target need to depend on the gch file, to make sure
it's built before any o file is built.
This class helps with some of these tasks, but it needs help from the build
system for writing dependencies to the gch files, for writing build commands
for the gch files, and for figuring out the location of the gch files.
"""
This class helps with some of these tasks, but it needs help from the build
system for writing dependencies to the gch files, for writing build commands
for the gch files, and for figuring out the location of the gch files.
"""
def __init__(
self, xcode_settings, gyp_path_to_build_path, gyp_path_to_build_output
):
"""If xcode_settings is None, all methods on this class are no-ops.
Args:
gyp_path_to_build_path: A function that takes a gyp-relative path,
and returns a path relative to the build directory.
gyp_path_to_build_output: A function that takes a gyp-relative path and
a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
to where the output of precompiling that path for that language
should be placed (without the trailing '.gch').
"""
Args:
gyp_path_to_build_path: A function that takes a gyp-relative path,
and returns a path relative to the build directory.
gyp_path_to_build_output: A function that takes a gyp-relative path and
a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
to where the output of precompiling that path for that language
should be placed (without the trailing '.gch').
"""
# This doesn't support per-configuration prefix headers. Good enough
# for now.
self.header = None
@ -1448,9 +1444,9 @@ class MacPrefixHeader:
def GetObjDependencies(self, sources, objs, arch=None):
"""Given a list of source files and the corresponding object files, returns
a list of (source, object, gch) tuples, where |gch| is the build-directory
relative path to the gch file each object file depends on. |compilable[i]|
has to be the source file belonging to |objs[i]|."""
a list of (source, object, gch) tuples, where |gch| is the build-directory
relative path to the gch file each object file depends on. |compilable[i]|
has to be the source file belonging to |objs[i]|."""
if not self.header or not self.compile_headers:
return []
@ -1471,8 +1467,8 @@ class MacPrefixHeader:
def GetPchBuildCommands(self, arch=None):
"""Returns [(path_to_gch, language_flag, language, header)].
|path_to_gch| and |header| are relative to the build directory.
"""
|path_to_gch| and |header| are relative to the build directory.
"""
if not self.header or not self.compile_headers:
return []
return [
@ -1509,7 +1505,8 @@ def XcodeVersion():
raise GypError("xcodebuild returned unexpected results")
version = version_list[0].split()[-1] # Last word on first line
build = version_list[-1].split()[-1] # Last word on last line
except GypError: # Xcode not installed so look for XCode Command Line Tools
except (GypError, OSError):
# Xcode not installed so look for XCode Command Line Tools
version = CLTVersion() # macOS Catalina returns 11.0.0.0.1.1567737322
if not version:
raise GypError("No Xcode or CLT version detected!")
@ -1537,26 +1534,28 @@ def CLTVersion():
FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI"
MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables"
regex = re.compile("version: (?P<version>.+)")
regex = re.compile(r"version: (?P<version>.+)")
for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]:
try:
output = GetStdout(["/usr/sbin/pkgutil", "--pkg-info", key])
return re.search(regex, output).groupdict()["version"]
except GypError:
if m := re.search(regex, output):
return m.groupdict()["version"]
except (GypError, OSError):
continue
regex = re.compile(r'Command Line Tools for Xcode\s+(?P<version>\S+)')
regex = re.compile(r"Command Line Tools for Xcode\s+(?P<version>\S+)")
try:
output = GetStdout(["/usr/sbin/softwareupdate", "--history"])
return re.search(regex, output).groupdict()["version"]
except GypError:
if m := re.search(regex, output):
return m.groupdict()["version"]
except (GypError, OSError):
return None
def GetStdoutQuiet(cmdlist):
"""Returns the content of standard output returned by invoking |cmdlist|.
Ignores the stderr.
Raises |GypError| if the command return with a non-zero return code."""
Ignores the stderr.
Raises |GypError| if the command return with a non-zero return code."""
job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = job.communicate()[0].decode("utf-8")
if job.returncode != 0:
@ -1566,7 +1565,7 @@ def GetStdoutQuiet(cmdlist):
def GetStdout(cmdlist):
"""Returns the content of standard output returned by invoking |cmdlist|.
Raises |GypError| if the command return with a non-zero return code."""
Raises |GypError| if the command return with a non-zero return code."""
job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
out = job.communicate()[0].decode("utf-8")
if job.returncode != 0:
@ -1577,9 +1576,9 @@ def GetStdout(cmdlist):
def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
"""Merges the global xcode_settings dictionary into each configuration of the
target represented by spec. For keys that are both in the global and the local
xcode_settings dict, the local key gets precedence.
"""
target represented by spec. For keys that are both in the global and the local
xcode_settings dict, the local key gets precedence.
"""
# The xcode generator special-cases global xcode_settings and does something
# that amounts to merging in the global xcode_settings into each local
# xcode_settings dict.
@ -1594,9 +1593,9 @@ def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
def IsMacBundle(flavor, spec):
"""Returns if |spec| should be treated as a bundle.
Bundles are directories with a certain subdirectory structure, instead of
just a single file. Bundle rules do not produce a binary but also package
resources into that directory."""
Bundles are directories with a certain subdirectory structure, instead of
just a single file. Bundle rules do not produce a binary but also package
resources into that directory."""
is_mac_bundle = (
int(spec.get("mac_xctest_bundle", 0)) != 0
or int(spec.get("mac_xcuitest_bundle", 0)) != 0
@ -1613,14 +1612,14 @@ def IsMacBundle(flavor, spec):
def GetMacBundleResources(product_dir, xcode_settings, resources):
"""Yields (output, resource) pairs for every resource in |resources|.
Only call this for mac bundle targets.
Only call this for mac bundle targets.
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
resources: A list of bundle resources, relative to the build directory.
"""
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
resources: A list of bundle resources, relative to the build directory.
"""
dest = os.path.join(product_dir, xcode_settings.GetBundleResourceFolder())
for res in resources:
output = dest
@ -1651,24 +1650,24 @@ def GetMacBundleResources(product_dir, xcode_settings, resources):
def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
"""Returns (info_plist, dest_plist, defines, extra_env), where:
* |info_plist| is the source plist path, relative to the
build directory,
* |dest_plist| is the destination plist path, relative to the
build directory,
* |defines| is a list of preprocessor defines (empty if the plist
shouldn't be preprocessed,
* |extra_env| is a dict of env variables that should be exported when
invoking |mac_tool copy-info-plist|.
* |info_plist| is the source plist path, relative to the
build directory,
* |dest_plist| is the destination plist path, relative to the
build directory,
* |defines| is a list of preprocessor defines (empty if the plist
shouldn't be preprocessed,
* |extra_env| is a dict of env variables that should be exported when
invoking |mac_tool copy-info-plist|.
Only call this for mac bundle targets.
Only call this for mac bundle targets.
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
Args:
product_dir: Path to the directory containing the output bundle,
relative to the build directory.
xcode_settings: The XcodeSettings of the current target.
gyp_to_build_path: A function that converts paths relative to the
current gyp file to paths relative to the build directory.
"""
info_plist = xcode_settings.GetPerTargetSetting("INFOPLIST_FILE")
if not info_plist:
return None, None, [], {}
@ -1706,18 +1705,18 @@ def _GetXcodeEnv(
xcode_settings, built_products_dir, srcroot, configuration, additional_settings=None
):
"""Return the environment variables that Xcode would set. See
http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153
for a full list.
http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153
for a full list.
Args:
xcode_settings: An XcodeSettings object. If this is None, this function
returns an empty dict.
built_products_dir: Absolute path to the built products dir.
srcroot: Absolute path to the source root.
configuration: The build configuration name.
additional_settings: An optional dict with more values to add to the
result.
"""
Args:
xcode_settings: An XcodeSettings object. If this is None, this function
returns an empty dict.
built_products_dir: Absolute path to the built products dir.
srcroot: Absolute path to the source root.
configuration: The build configuration name.
additional_settings: An optional dict with more values to add to the
result.
"""
if not xcode_settings:
return {}
@ -1771,27 +1770,25 @@ def _GetXcodeEnv(
)
env["CONTENTS_FOLDER_PATH"] = xcode_settings.GetBundleContentsFolderPath()
env["EXECUTABLE_FOLDER_PATH"] = xcode_settings.GetBundleExecutableFolderPath()
env[
"UNLOCALIZED_RESOURCES_FOLDER_PATH"
] = xcode_settings.GetBundleResourceFolder()
env["UNLOCALIZED_RESOURCES_FOLDER_PATH"] = (
xcode_settings.GetBundleResourceFolder()
)
env["JAVA_FOLDER_PATH"] = xcode_settings.GetBundleJavaFolderPath()
env["FRAMEWORKS_FOLDER_PATH"] = xcode_settings.GetBundleFrameworksFolderPath()
env[
"SHARED_FRAMEWORKS_FOLDER_PATH"
] = xcode_settings.GetBundleSharedFrameworksFolderPath()
env[
"SHARED_SUPPORT_FOLDER_PATH"
] = xcode_settings.GetBundleSharedSupportFolderPath()
env["SHARED_FRAMEWORKS_FOLDER_PATH"] = (
xcode_settings.GetBundleSharedFrameworksFolderPath()
)
env["SHARED_SUPPORT_FOLDER_PATH"] = (
xcode_settings.GetBundleSharedSupportFolderPath()
)
env["PLUGINS_FOLDER_PATH"] = xcode_settings.GetBundlePlugInsFolderPath()
env["XPCSERVICES_FOLDER_PATH"] = xcode_settings.GetBundleXPCServicesFolderPath()
env["INFOPLIST_PATH"] = xcode_settings.GetBundlePlistPath()
env["WRAPPER_NAME"] = xcode_settings.GetWrapperName()
install_name = xcode_settings.GetInstallName()
if install_name:
if install_name := xcode_settings.GetInstallName():
env["LD_DYLIB_INSTALL_NAME"] = install_name
install_name_base = xcode_settings.GetInstallNameBase()
if install_name_base:
if install_name_base := xcode_settings.GetInstallNameBase():
env["DYLIB_INSTALL_NAME_BASE"] = install_name_base
xcode_version, _ = XcodeVersion()
if xcode_version >= "0500" and not env.get("SDKROOT"):
@ -1819,8 +1816,8 @@ def _GetXcodeEnv(
def _NormalizeEnvVarReferences(str):
"""Takes a string containing variable references in the form ${FOO}, $(FOO),
or $FOO, and returns a string with all variable references in the form ${FOO}.
"""
or $FOO, and returns a string with all variable references in the form ${FOO}.
"""
# $FOO -> ${FOO}
str = re.sub(r"\$([a-zA-Z_][a-zA-Z0-9_]*)", r"${\1}", str)
@ -1836,9 +1833,9 @@ def _NormalizeEnvVarReferences(str):
def ExpandEnvVars(string, expansions):
"""Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the
expansions list. If the variable expands to something that references
another variable, this variable is expanded as well if it's in env --
until no variables present in env are left."""
expansions list. If the variable expands to something that references
another variable, this variable is expanded as well if it's in env --
until no variables present in env are left."""
for k, v in reversed(expansions):
string = string.replace("${" + k + "}", v)
string = string.replace("$(" + k + ")", v)
@ -1848,18 +1845,18 @@ def ExpandEnvVars(string, expansions):
def _TopologicallySortedEnvVarKeys(env):
"""Takes a dict |env| whose values are strings that can refer to other keys,
for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
env such that key2 is after key1 in L if env[key2] refers to env[key1].
for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
env such that key2 is after key1 in L if env[key2] refers to env[key1].
Throws an Exception in case of dependency cycles.
"""
Throws an Exception in case of dependency cycles.
"""
# Since environment variables can refer to other variables, the evaluation
# order is important. Below is the logic to compute the dependency graph
# and sort it.
regex = re.compile(r"\$\{([a-zA-Z0-9\-_]+)\}")
def GetEdges(node):
# Use a definition of edges such that user_of_variable -> used_varible.
# Use a definition of edges such that user_of_variable -> used_variable.
# This happens to be easier in this case, since a variable's
# definition contains all variables it references in a single string.
# We can then reverse the result of the topological sort at the end.
@ -1893,7 +1890,7 @@ def GetSortedXcodeEnv(
def GetSpecPostbuildCommands(spec, quiet=False):
"""Returns the list of postbuilds explicitly defined on |spec|, in a form
executable by a shell."""
executable by a shell."""
postbuilds = []
for postbuild in spec.get("postbuilds", []):
if not quiet:
@ -1907,7 +1904,7 @@ def GetSpecPostbuildCommands(spec, quiet=False):
def _HasIOSTarget(targets):
"""Returns true if any target contains the iOS specific key
IPHONEOS_DEPLOYMENT_TARGET."""
IPHONEOS_DEPLOYMENT_TARGET."""
for target_dict in targets.values():
for config in target_dict["configurations"].values():
if config.get("xcode_settings", {}).get("IPHONEOS_DEPLOYMENT_TARGET"):
@ -1917,7 +1914,7 @@ def _HasIOSTarget(targets):
def _AddIOSDeviceConfigurations(targets):
"""Clone all targets and append -iphoneos to the name. Configure these targets
to build for iOS devices and use correct architectures for those builds."""
to build for iOS devices and use correct architectures for those builds."""
for target_dict in targets.values():
toolset = target_dict["toolset"]
configs = target_dict["configurations"]
@ -1933,7 +1930,7 @@ def _AddIOSDeviceConfigurations(targets):
def CloneConfigurationForDeviceAndEmulator(target_dicts):
"""If |target_dicts| contains any iOS targets, automatically create -iphoneos
targets for iOS device builds."""
targets for iOS device builds."""
if _HasIOSTarget(target_dicts):
return _AddIOSDeviceConfigurations(target_dicts)
return target_dicts

View file

@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Unit tests for the xcode_emulation.py file."""
import sys
import unittest
from gyp.xcode_emulation import XcodeSettings
class TestXcodeSettings(unittest.TestCase):
def setUp(self):
if sys.platform != "darwin":
self.skipTest("This test only runs on macOS")
def test_GetCflags(self):
target = {
"type": "static_library",
"configurations": {
"Release": {},
},
}
configuration_name = "Release"
xcode_settings = XcodeSettings(target)
cflags = xcode_settings.GetCflags(configuration_name, "arm64")
# Do not quote `-arch arm64` with spaces in one string.
self.assertEqual(
cflags,
["-fasm-blocks", "-mpascal-strings", "-Os", "-gdwarf-2", "-arch", "arm64"],
)
def GypToBuildPath(self, path):
return path
def test_GetLdflags(self):
target = {
"type": "static_library",
"configurations": {
"Release": {},
},
}
configuration_name = "Release"
xcode_settings = XcodeSettings(target)
ldflags = xcode_settings.GetLdflags(
configuration_name, "PRODUCT_DIR", self.GypToBuildPath, "arm64"
)
# Do not quote `-arch arm64` with spaces in one string.
self.assertEqual(ldflags, ["-arch", "arm64", "-LPRODUCT_DIR"])
if __name__ == "__main__":
unittest.main()

View file

@ -13,15 +13,16 @@ of targets within Xcode.
"""
import errno
import gyp.generator.ninja
import os
import re
import xml.sax.saxutils
import gyp.generator.ninja
def _WriteWorkspace(main_gyp, sources_gyp, params):
""" Create a workspace to wrap main and sources gyp paths. """
(build_file_root, build_file_ext) = os.path.splitext(main_gyp)
"""Create a workspace to wrap main and sources gyp paths."""
(build_file_root, _build_file_ext) = os.path.splitext(main_gyp)
workspace_path = build_file_root + ".xcworkspace"
options = params["options"]
if options.generator_output:
@ -56,7 +57,7 @@ def _WriteWorkspace(main_gyp, sources_gyp, params):
def _TargetFromSpec(old_spec, params):
""" Create fake target for xcode-ninja wrapper. """
"""Create fake target for xcode-ninja wrapper."""
# Determine ninja top level build dir (e.g. /path/to/out).
ninja_toplevel = None
jobs = 0
@ -69,12 +70,11 @@ def _TargetFromSpec(old_spec, params):
target_name = old_spec.get("target_name")
product_name = old_spec.get("product_name", target_name)
product_extension = old_spec.get("product_extension")
ninja_target = {}
ninja_target["target_name"] = target_name
ninja_target["product_name"] = product_name
if product_extension:
if product_extension := old_spec.get("product_extension"):
ninja_target["product_extension"] = product_extension
ninja_target["toolset"] = old_spec.get("toolset")
ninja_target["default_configuration"] = old_spec.get("default_configuration")
@ -102,9 +102,9 @@ def _TargetFromSpec(old_spec, params):
new_xcode_settings[key] = old_xcode_settings[key]
ninja_target["configurations"][config] = {}
ninja_target["configurations"][config][
"xcode_settings"
] = new_xcode_settings
ninja_target["configurations"][config]["xcode_settings"] = (
new_xcode_settings
)
ninja_target["mac_bundle"] = old_spec.get("mac_bundle", 0)
ninja_target["mac_xctest_bundle"] = old_spec.get("mac_xctest_bundle", 0)
@ -137,13 +137,13 @@ def _TargetFromSpec(old_spec, params):
def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
"""Limit targets for Xcode wrapper.
Xcode sometimes performs poorly with too many targets, so only include
proper executable targets, with filters to customize.
Arguments:
target_extras: Regular expression to always add, matching any target.
executable_target_pattern: Regular expression limiting executable targets.
spec: Specifications for target.
"""
Xcode sometimes performs poorly with too many targets, so only include
proper executable targets, with filters to customize.
Arguments:
target_extras: Regular expression to always add, matching any target.
executable_target_pattern: Regular expression limiting executable targets.
spec: Specifications for target.
"""
target_name = spec.get("target_name")
# Always include targets matching target_extras.
if target_extras is not None and re.search(target_extras, target_name):
@ -154,7 +154,6 @@ def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
spec.get("type", "") == "executable"
and spec.get("product_extension", "") != "bundle"
):
# If there is a filter and the target does not match, exclude the target.
if executable_target_pattern is not None:
if not re.search(executable_target_pattern, target_name):
@ -166,14 +165,14 @@ def IsValidTargetForWrapper(target_extras, executable_target_pattern, spec):
def CreateWrapper(target_list, target_dicts, data, params):
"""Initialize targets for the ninja wrapper.
This sets up the necessary variables in the targets to generate Xcode projects
that use ninja as an external builder.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
data: Dict of flattened build files keyed on gyp path.
params: Dict of global options for gyp.
"""
This sets up the necessary variables in the targets to generate Xcode projects
that use ninja as an external builder.
Arguments:
target_list: List of target pairs: 'base/base.gyp:base'.
target_dicts: Dict of target properties keyed on target pair.
data: Dict of flattened build files keyed on gyp path.
params: Dict of global options for gyp.
"""
orig_gyp = params["build_files"][0]
for gyp_name, gyp_dict in data.items():
if gyp_name == orig_gyp:

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,6 @@ Working around this: http://bugs.python.org/issue5752
TODO(bradnelson): Consider dropping this when we drop XP support.
"""
import xml.dom.minidom