mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 06:31:50 +08:00
ios: Update build scripts to support Apple Silicon simulators.
Also update mini_chromium to 5654edb422 for target_environment arg. Change-Id: If350938bbeaddbdda123c2f0e9ff978075a60370 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3558170 Reviewed-by: Rohit Rao <rohitrao@chromium.org> Commit-Queue: Justin Cohen <justincohen@chromium.org>
This commit is contained in:
parent
20d6dee037
commit
fa01762894
2
DEPS
2
DEPS
@ -39,7 +39,7 @@ deps = {
|
||||
'e1e7b0ad8ee99a875b272c8e33e308472e897660',
|
||||
'crashpad/third_party/mini_chromium/mini_chromium':
|
||||
Var('chromium_git') + '/chromium/mini_chromium@' +
|
||||
'f87a38442a9e7ba88d1c4f479e9167927eae84ed',
|
||||
'5654edb4225bcad13901155c819febb5748e502b',
|
||||
'crashpad/third_party/libfuzzer/src':
|
||||
Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
|
||||
'fda403cf93ecb8792cb1d061564d89a6553ca020',
|
||||
|
@ -26,14 +26,146 @@ import argparse
|
||||
import collections
|
||||
import copy
|
||||
import filecmp
|
||||
import json
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
LLDBINIT_PATH = '$(PROJECT_DIR)/.lldbinit'
|
||||
|
||||
PYTHON_RE = re.compile('[ /]python[23]?$')
|
||||
|
||||
XCTEST_PRODUCT_TYPES = frozenset((
|
||||
'com.apple.product-type.bundle.unit-test',
|
||||
'com.apple.product-type.bundle.ui-testing',
|
||||
))
|
||||
|
||||
SCHEME_PRODUCT_TYPES = frozenset((
|
||||
'com.apple.product-type.app-extension',
|
||||
'com.apple.product-type.application',
|
||||
'com.apple.product-type.framework'
|
||||
))
|
||||
|
||||
|
||||
class Template(string.Template):
|
||||
|
||||
"""A subclass of string.Template that changes delimiter."""
|
||||
|
||||
delimiter = '@'
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def LoadSchemeTemplate(root, name):
|
||||
"""Return a string.Template object for scheme file loaded relative to root."""
|
||||
path = os.path.join(root, 'build', 'ios', name)
|
||||
with open(path) as file:
|
||||
return Template(file.read())
|
||||
|
||||
|
||||
def CreateIdentifier(str_id):
|
||||
"""Return a 24 characters string that can be used as an identifier."""
|
||||
return hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper()
|
||||
|
||||
|
||||
def GenerateSchemeForTarget(root, project, old_project, name, path, tests):
|
||||
"""Generates the .xcsheme file for target named |name|.
|
||||
|
||||
The file is generated in the new project schemes directory from a template.
|
||||
If there is an existing previous project, then the old scheme file is copied
|
||||
and the lldbinit setting is set. If lldbinit setting is already correct, the
|
||||
file is not modified, just copied.
|
||||
"""
|
||||
project_name = os.path.basename(project)
|
||||
relative_path = os.path.join('xcshareddata', 'xcschemes', name + '.xcscheme')
|
||||
identifier = CreateIdentifier('%s %s' % (name, path))
|
||||
|
||||
scheme_path = os.path.join(project, relative_path)
|
||||
if not os.path.isdir(os.path.dirname(scheme_path)):
|
||||
os.makedirs(os.path.dirname(scheme_path))
|
||||
|
||||
old_scheme_path = os.path.join(old_project, relative_path)
|
||||
if os.path.exists(old_scheme_path):
|
||||
made_changes = False
|
||||
|
||||
tree = xml.etree.ElementTree.parse(old_scheme_path)
|
||||
tree_root = tree.getroot()
|
||||
|
||||
for reference in tree_root.findall('.//BuildableReference'):
|
||||
for (attr, value) in (
|
||||
('BuildableName', path),
|
||||
('BlueprintName', name),
|
||||
('BlueprintIdentifier', identifier)):
|
||||
if reference.get(attr) != value:
|
||||
reference.set(attr, value)
|
||||
made_changes = True
|
||||
|
||||
for child in tree_root:
|
||||
if child.tag not in ('TestAction', 'LaunchAction'):
|
||||
continue
|
||||
|
||||
if child.get('customLLDBInitFile') != LLDBINIT_PATH:
|
||||
child.set('customLLDBInitFile', LLDBINIT_PATH)
|
||||
made_changes = True
|
||||
|
||||
# Override the list of testables.
|
||||
if child.tag == 'TestAction':
|
||||
for subchild in child:
|
||||
if subchild.tag != 'Testables':
|
||||
continue
|
||||
|
||||
for elt in list(subchild):
|
||||
subchild.remove(elt)
|
||||
|
||||
if tests:
|
||||
template = LoadSchemeTemplate(root, 'xcodescheme-testable.template')
|
||||
for (key, test_path, test_name) in sorted(tests):
|
||||
testable = ''.join(template.substitute(
|
||||
BLUEPRINT_IDENTIFIER=key,
|
||||
BUILDABLE_NAME=test_path,
|
||||
BLUEPRINT_NAME=test_name,
|
||||
PROJECT_NAME=project_name))
|
||||
|
||||
testable_elt = xml.etree.ElementTree.fromstring(testable)
|
||||
subchild.append(testable_elt)
|
||||
|
||||
if made_changes:
|
||||
tree.write(scheme_path, xml_declaration=True, encoding='UTF-8')
|
||||
|
||||
else:
|
||||
shutil.copyfile(old_scheme_path, scheme_path)
|
||||
|
||||
else:
|
||||
|
||||
testables = ''
|
||||
if tests:
|
||||
template = LoadSchemeTemplate(root, 'xcodescheme-testable.template')
|
||||
testables = '\n' + ''.join(
|
||||
template.substitute(
|
||||
BLUEPRINT_IDENTIFIER=key,
|
||||
BUILDABLE_NAME=test_path,
|
||||
BLUEPRINT_NAME=test_name,
|
||||
PROJECT_NAME=project_name)
|
||||
for (key, test_path, test_name) in sorted(tests)).rstrip()
|
||||
|
||||
template = LoadSchemeTemplate(root, 'xcodescheme.template')
|
||||
|
||||
with open(scheme_path, 'w') as scheme_file:
|
||||
scheme_file.write(
|
||||
template.substitute(
|
||||
TESTABLES=testables,
|
||||
LLDBINIT_PATH=LLDBINIT_PATH,
|
||||
BLUEPRINT_IDENTIFIER=identifier,
|
||||
BUILDABLE_NAME=path,
|
||||
BLUEPRINT_NAME=name,
|
||||
PROJECT_NAME=project_name))
|
||||
|
||||
|
||||
class XcodeProject(object):
|
||||
@ -46,7 +178,7 @@ class XcodeProject(object):
|
||||
while True:
|
||||
self.counter += 1
|
||||
str_id = "%s %s %d" % (parent_name, obj['isa'], self.counter)
|
||||
new_id = hashlib.sha1(str_id.encode("utf-8")).hexdigest()[:24].upper()
|
||||
new_id = CreateIdentifier(str_id)
|
||||
|
||||
# Make sure ID is unique. It's possible there could be an id conflict
|
||||
# since this is run after GN runs.
|
||||
@ -54,6 +186,93 @@ class XcodeProject(object):
|
||||
self.objects[new_id] = obj
|
||||
return new_id
|
||||
|
||||
def IterObjectsByIsa(self, isa):
|
||||
"""Iterates overs objects of the |isa| type."""
|
||||
for key, obj in self.objects.items():
|
||||
if obj['isa'] == isa:
|
||||
yield (key, obj)
|
||||
|
||||
def IterNativeTargetByProductType(self, product_types):
|
||||
"""Iterates over PBXNativeTarget objects of any |product_types| types."""
|
||||
for key, obj in self.IterObjectsByIsa('PBXNativeTarget'):
|
||||
if obj['productType'] in product_types:
|
||||
yield (key, obj)
|
||||
|
||||
def UpdateBuildScripts(self):
|
||||
"""Update build scripts to respect configuration and platforms."""
|
||||
for key, obj in self.IterObjectsByIsa('PBXShellScriptBuildPhase'):
|
||||
|
||||
shell_path = obj['shellPath']
|
||||
shell_code = obj['shellScript']
|
||||
if shell_path.endswith('/sh'):
|
||||
shell_code = shell_code.replace(
|
||||
'ninja -C .',
|
||||
'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
|
||||
elif PYTHON_RE.search(shell_path):
|
||||
shell_code = shell_code.replace(
|
||||
'''ninja_params = [ '-C', '.' ]''',
|
||||
'''ninja_params = [ '-C', '../' + os.environ['CONFIGURATION']'''
|
||||
''' + os.environ['EFFECTIVE_PLATFORM_NAME'] ]''')
|
||||
|
||||
# Replace the build script in the object.
|
||||
obj['shellScript'] = shell_code
|
||||
|
||||
|
||||
def UpdateBuildConfigurations(self, configurations):
|
||||
"""Add new configurations, using the first one as default."""
|
||||
|
||||
# Create a list with all the objects of interest. This is needed
|
||||
# because objects will be added to/removed from the project upon
|
||||
# iterating this list and python dictionaries cannot be mutated
|
||||
# during iteration.
|
||||
for key, obj in list(self.IterObjectsByIsa('XCConfigurationList')):
|
||||
# Use the first build configuration as template for creating all the
|
||||
# new build configurations.
|
||||
build_config_template = self.objects[obj['buildConfigurations'][0]]
|
||||
build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \
|
||||
'$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'
|
||||
|
||||
|
||||
# Remove the existing build configurations from the project before
|
||||
# creating the new ones.
|
||||
for build_config_id in obj['buildConfigurations']:
|
||||
del self.objects[build_config_id]
|
||||
obj['buildConfigurations'] = []
|
||||
|
||||
for configuration in configurations:
|
||||
build_config = copy.copy(build_config_template)
|
||||
build_config['name'] = configuration
|
||||
build_config_id = self.AddObject('products', build_config)
|
||||
obj['buildConfigurations'].append(build_config_id)
|
||||
|
||||
def GetHostMappingForXCTests(self):
|
||||
"""Returns a dict from targets to the list of their xctests modules."""
|
||||
mapping = collections.defaultdict(list)
|
||||
for key, obj in self.IterNativeTargetByProductType(XCTEST_PRODUCT_TYPES):
|
||||
build_config_lists_id = obj['buildConfigurationList']
|
||||
build_configs = self.objects[build_config_lists_id]['buildConfigurations']
|
||||
|
||||
# Use the first build configuration to get the name of the host target.
|
||||
# This is arbitrary, but since the build configuration are all identical
|
||||
# after UpdateBuildConfiguration, except for their 'name', it is fine.
|
||||
build_config = self.objects[build_configs[0]]
|
||||
if obj['productType'] == 'com.apple.product-type.bundle.unit-test':
|
||||
# The test_host value will look like this:
|
||||
# `$(BUILD_PRODUCTS_DIR)/host_app_name.app/host_app_name`
|
||||
#
|
||||
# Extract the `host_app_name.app` part as key for the output.
|
||||
test_host_path = build_config['buildSettings']['TEST_HOST']
|
||||
test_host_name = os.path.basename(os.path.dirname(test_host_path))
|
||||
else:
|
||||
test_host_name = build_config['buildSettings']['TEST_TARGET_NAME']
|
||||
|
||||
test_name = obj['name']
|
||||
test_path = self.objects[obj['productReference']]['path']
|
||||
|
||||
mapping[test_host_name].append((key, test_name, test_path))
|
||||
|
||||
return dict(mapping)
|
||||
|
||||
|
||||
def check_output(command):
|
||||
"""Wrapper around subprocess.check_output that decode output as utf-8."""
|
||||
@ -100,7 +319,7 @@ def WriteXcodeProject(output_path, json_string):
|
||||
os.path.join(output_path, 'project.pbxproj'))
|
||||
|
||||
|
||||
def UpdateXcodeProject(project_dir, configurations, root_dir):
|
||||
def UpdateXcodeProject(project_dir, old_project_dir, configurations, root_dir):
|
||||
"""Update inplace Xcode project to support multiple configurations.
|
||||
|
||||
Args:
|
||||
@ -113,41 +332,25 @@ def UpdateXcodeProject(project_dir, configurations, root_dir):
|
||||
json_data = json.loads(LoadXcodeProjectAsJSON(project_dir))
|
||||
project = XcodeProject(json_data['objects'])
|
||||
|
||||
objects_to_remove = []
|
||||
for value in list(project.objects.values()):
|
||||
isa = value['isa']
|
||||
project.UpdateBuildScripts()
|
||||
project.UpdateBuildConfigurations(configurations)
|
||||
|
||||
# Teach build shell script to look for the configuration and platform.
|
||||
if isa == 'PBXShellScriptBuildPhase':
|
||||
shell_path = value['shellPath']
|
||||
if shell_path.endswith('/sh'):
|
||||
value['shellScript'] = value['shellScript'].replace(
|
||||
'ninja -C .',
|
||||
'ninja -C "../${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}"')
|
||||
elif re.search('[ /]python[23]?$', shell_path):
|
||||
value['shellScript'] = value['shellScript'].replace(
|
||||
'ninja_params = [ \'-C\', \'.\' ]',
|
||||
'ninja_params = [ \'-C\', \'../\' + os.environ[\'CONFIGURATION\']'
|
||||
' + os.environ[\'EFFECTIVE_PLATFORM_NAME\'] ]')
|
||||
mapping = project.GetHostMappingForXCTests()
|
||||
|
||||
# Add new configuration, using the first one as default.
|
||||
if isa == 'XCConfigurationList':
|
||||
value['defaultConfigurationName'] = configurations[0]
|
||||
objects_to_remove.extend(value['buildConfigurations'])
|
||||
# Generate schemes for application, extensions and framework targets
|
||||
for key, obj in project.IterNativeTargetByProductType(SCHEME_PRODUCT_TYPES):
|
||||
product = project.objects[obj['productReference']]
|
||||
product_path = product['path']
|
||||
|
||||
build_config_template = project.objects[value['buildConfigurations'][0]]
|
||||
build_config_template['buildSettings']['CONFIGURATION_BUILD_DIR'] = \
|
||||
'$(PROJECT_DIR)/../$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)'
|
||||
# For XCTests, the key is the product path, while for XCUITests, the key
|
||||
# is the target name. Use a sum of both possible keys (there should not
|
||||
# be overlaps since different hosts are used for XCTests and XCUITests
|
||||
# but this make the code simpler).
|
||||
tests = mapping.get(product_path, []) + mapping.get(obj['name'], [])
|
||||
GenerateSchemeForTarget(
|
||||
root_dir, project_dir, old_project_dir,
|
||||
obj['name'], product_path, tests)
|
||||
|
||||
value['buildConfigurations'] = []
|
||||
for configuration in configurations:
|
||||
new_build_config = copy.copy(build_config_template)
|
||||
new_build_config['name'] = configuration
|
||||
value['buildConfigurations'].append(
|
||||
project.AddObject('products', new_build_config))
|
||||
|
||||
for object_id in objects_to_remove:
|
||||
del project.objects[object_id]
|
||||
|
||||
source = GetOrCreateRootGroup(project, json_data['rootObject'], 'Source')
|
||||
AddMarkdownToProject(project, root_dir, source)
|
||||
@ -274,7 +477,7 @@ def GetFolderForPath(project, group_object, path):
|
||||
return group_object
|
||||
|
||||
|
||||
def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations):
|
||||
def ConvertGnXcodeProject(root_dir, proj_name, input_dir, output_dir, configs):
|
||||
'''Tweak the Xcode project generated by gn to support multiple configurations.
|
||||
|
||||
The Xcode projects generated by "gn gen --ide" only supports a single
|
||||
@ -284,34 +487,22 @@ def ConvertGnXcodeProject(root_dir, input_dir, output_dir, configurations):
|
||||
to select them in Xcode).
|
||||
|
||||
Args:
|
||||
root_dir: directory that is the root of the project
|
||||
proj_name: name of the Xcode project "file" (usually `all.xcodeproj`)
|
||||
input_dir: directory containing the XCode projects created by "gn gen --ide"
|
||||
output_dir: directory where the tweaked Xcode projects will be saved
|
||||
configurations: list of string corresponding to the configurations that
|
||||
need to be supported by the tweaked Xcode projects, must contains at
|
||||
least one value.
|
||||
configs: list of string corresponding to the configurations that need to be
|
||||
supported by the tweaked Xcode projects, must contains at least one
|
||||
value.
|
||||
'''
|
||||
|
||||
# Update the project (supports legacy name "products.xcodeproj" or the new
|
||||
# project name "all.xcodeproj").
|
||||
for project_name in ('all.xcodeproj', 'products.xcodeproj'):
|
||||
if os.path.exists(os.path.join(input_dir, project_name)):
|
||||
UpdateXcodeProject(
|
||||
os.path.join(input_dir, project_name),
|
||||
configurations, root_dir)
|
||||
UpdateXcodeProject(
|
||||
os.path.join(input_dir, proj_name),
|
||||
os.path.join(output_dir, proj_name),
|
||||
configs, root_dir)
|
||||
|
||||
CopyTreeIfChanged(os.path.join(input_dir, project_name),
|
||||
os.path.join(output_dir, project_name))
|
||||
|
||||
else:
|
||||
shutil.rmtree(os.path.join(output_dir, project_name), ignore_errors=True)
|
||||
|
||||
# Copy all.xcworkspace if it exists (will be removed in a future gn version).
|
||||
workspace_name = 'all.xcworkspace'
|
||||
if os.path.exists(os.path.join(input_dir, workspace_name)):
|
||||
CopyTreeIfChanged(os.path.join(input_dir, workspace_name),
|
||||
os.path.join(output_dir, workspace_name))
|
||||
else:
|
||||
shutil.rmtree(os.path.join(output_dir, workspace_name), ignore_errors=True)
|
||||
CopyTreeIfChanged(os.path.join(input_dir, proj_name),
|
||||
os.path.join(output_dir, proj_name))
|
||||
|
||||
|
||||
def Main(args):
|
||||
@ -329,33 +520,30 @@ def Main(args):
|
||||
parser.add_argument(
|
||||
'--root', type=os.path.abspath, required=True,
|
||||
help='root directory of the project')
|
||||
parser.add_argument(
|
||||
'--project-name', default='all.xcodeproj', dest='proj_name',
|
||||
help='name of the Xcode project (default: %(default)s)')
|
||||
args = parser.parse_args(args)
|
||||
|
||||
if not os.path.isdir(args.input):
|
||||
sys.stderr.write('Input directory does not exists.\n')
|
||||
return 1
|
||||
|
||||
# Depending on the version of "gn", there should be either one project file
|
||||
# named "all.xcodeproj" or a project file named "products.xcodeproj" and a
|
||||
# workspace named "all.xcworkspace".
|
||||
required_files_sets = [
|
||||
set(("all.xcodeproj",)),
|
||||
set(("products.xcodeproj", "all.xcworkspace")),
|
||||
]
|
||||
|
||||
for required_files in required_files_sets:
|
||||
if required_files.issubset(os.listdir(args.input)):
|
||||
break
|
||||
else:
|
||||
if args.proj_name not in os.listdir(args.input):
|
||||
sys.stderr.write(
|
||||
'Input directory does not contain all necessary Xcode projects.\n')
|
||||
'Input directory does not contain the Xcode project.\n')
|
||||
return 1
|
||||
|
||||
if not args.configurations:
|
||||
sys.stderr.write('At least one configuration required, see --add-config.\n')
|
||||
return 1
|
||||
|
||||
ConvertGnXcodeProject(args.root, args.input, args.output, args.configurations)
|
||||
ConvertGnXcodeProject(
|
||||
args.root,
|
||||
args.proj_name,
|
||||
args.input,
|
||||
args.output,
|
||||
args.configurations)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(Main(sys.argv[1:]))
|
||||
|
@ -15,325 +15,391 @@
|
||||
# limitations under the License.
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import convert_gn_xcodeproj
|
||||
import errno
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import configparser
|
||||
import io
|
||||
|
||||
SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator')
|
||||
SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official', 'Coverage')
|
||||
|
||||
SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator', 'maccatalyst')
|
||||
SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official')
|
||||
|
||||
# Pattern matching lines from ~/.lldbinit that must not be copied to the
|
||||
# generated .lldbinit file. They match what the user were told to add to
|
||||
# their global ~/.lldbinit file before setup-gn.py was updated to generate
|
||||
# a project specific file and thus must not be copied as they would cause
|
||||
# the settings to be overwritten.
|
||||
LLDBINIT_SKIP_PATTERNS = (
|
||||
re.compile('^script sys.path\\[:0\\] = \\[\'.*/src/tools/lldb\'\\]$'),
|
||||
re.compile('^script import lldbinit$'),
|
||||
re.compile('^settings append target.source-map .* /google/src/.*$'),
|
||||
)
|
||||
|
||||
|
||||
def HostCpuArch():
|
||||
'''Returns the arch of the host cpu for GN.'''
|
||||
HOST_CPU_ARCH = {
|
||||
'arm64': '"arm64"',
|
||||
'x86_64': '"x64"',
|
||||
}
|
||||
return HOST_CPU_ARCH[platform.machine()]
|
||||
|
||||
|
||||
class ConfigParserWithStringInterpolation(configparser.ConfigParser):
|
||||
'''A .ini file parser that supports strings and environment variables.'''
|
||||
|
||||
ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)')
|
||||
'''A .ini file parser that supports strings and environment variables.'''
|
||||
|
||||
def values(self, section):
|
||||
return [self._UnquoteString(self._ExpandEnvVar(kv[1])) for kv in configparser.ConfigParser.items(self, section)]
|
||||
ENV_VAR_PATTERN = re.compile(r'\$([A-Za-z0-9_]+)')
|
||||
|
||||
def getstring(self, section, option):
|
||||
return self._UnquoteString(self._ExpandEnvVar(self.get(section,
|
||||
option)))
|
||||
def values(self, section):
|
||||
return filter(
|
||||
lambda val: val != '',
|
||||
map(lambda kv: self._UnquoteString(self._ExpandEnvVar(kv[1])),
|
||||
configparser.ConfigParser.items(self, section)))
|
||||
|
||||
def _UnquoteString(self, string):
|
||||
if not string or string[0] != '"' or string[-1] != '"':
|
||||
return string
|
||||
return string[1:-1]
|
||||
def getstring(self, section, option, fallback=''):
|
||||
try:
|
||||
raw_value = self.get(section, option)
|
||||
except configparser.NoOptionError:
|
||||
return fallback
|
||||
return self._UnquoteString(self._ExpandEnvVar(raw_value))
|
||||
|
||||
def _ExpandEnvVar(self, value):
|
||||
match = self.ENV_VAR_PATTERN.search(value)
|
||||
if not match:
|
||||
return value
|
||||
name, (begin, end) = match.group(1), match.span(0)
|
||||
prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:])
|
||||
return prefix + os.environ.get(name, '') + suffix
|
||||
def _UnquoteString(self, string):
|
||||
if not string or string[0] != '"' or string[-1] != '"':
|
||||
return string
|
||||
return string[1:-1]
|
||||
|
||||
def _ExpandEnvVar(self, value):
|
||||
match = self.ENV_VAR_PATTERN.search(value)
|
||||
if not match:
|
||||
return value
|
||||
name, (begin, end) = match.group(1), match.span(0)
|
||||
prefix, suffix = value[:begin], self._ExpandEnvVar(value[end:])
|
||||
return prefix + os.environ.get(name, '') + suffix
|
||||
|
||||
|
||||
class GnGenerator(object):
|
||||
'''Holds configuration for a build and method to generate gn default
|
||||
files.'''
|
||||
|
||||
FAT_BUILD_DEFAULT_ARCH = '64-bit'
|
||||
'''Holds configuration for a build and method to generate gn default files.'''
|
||||
|
||||
TARGET_CPU_VALUES = {
|
||||
'iphoneos': {
|
||||
'32-bit': '"arm"',
|
||||
'64-bit': '"arm64"',
|
||||
},
|
||||
'iphonesimulator': {
|
||||
'32-bit': '"x86"',
|
||||
'64-bit': '"x64"',
|
||||
}
|
||||
}
|
||||
FAT_BUILD_DEFAULT_ARCH = '64-bit'
|
||||
|
||||
def __init__(self, settings, config, target):
|
||||
assert target in SUPPORTED_TARGETS
|
||||
assert config in SUPPORTED_CONFIGS
|
||||
self._settings = settings
|
||||
self._config = config
|
||||
self._target = target
|
||||
TARGET_CPU_VALUES = {
|
||||
'iphoneos': '"arm64"',
|
||||
'iphonesimulator': HostCpuArch(),
|
||||
'maccatalyst': HostCpuArch(),
|
||||
}
|
||||
|
||||
def _GetGnArgs(self):
|
||||
"""Build the list of arguments to pass to gn.
|
||||
TARGET_ENVIRONMENT_VALUES = {
|
||||
'iphoneos': '"device"',
|
||||
'iphonesimulator': '"simulator"',
|
||||
'maccatalyst': '"catalyst"'
|
||||
}
|
||||
|
||||
Returns:
|
||||
A list of tuple containing gn variable names and variable values (it
|
||||
is not a dictionary as the order needs to be preserved).
|
||||
"""
|
||||
args = []
|
||||
def __init__(self, settings, config, target):
|
||||
assert target in SUPPORTED_TARGETS
|
||||
assert config in SUPPORTED_CONFIGS
|
||||
self._settings = settings
|
||||
self._config = config
|
||||
self._target = target
|
||||
|
||||
args.append(('is_debug', self._config in ('Debug', 'Coverage')))
|
||||
def _GetGnArgs(self, extra_args=None):
|
||||
"""Build the list of arguments to pass to gn.
|
||||
|
||||
if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1':
|
||||
args.append(('use_system_xcode', False))
|
||||
Returns:
|
||||
A list of tuple containing gn variable names and variable values (it
|
||||
is not a dictionary as the order needs to be preserved).
|
||||
"""
|
||||
args = []
|
||||
|
||||
cpu_values = self.TARGET_CPU_VALUES[self._target]
|
||||
build_arch = self._settings.getstring('build', 'arch')
|
||||
if build_arch == 'fat':
|
||||
target_cpu = cpu_values[self.FAT_BUILD_DEFAULT_ARCH]
|
||||
args.append(('target_cpu', target_cpu))
|
||||
args.append(
|
||||
('additional_target_cpus',
|
||||
[cpu for cpu in cpu_values.values() if cpu != target_cpu]))
|
||||
else:
|
||||
args.append(('target_cpu', cpu_values[build_arch]))
|
||||
is_debug = self._config == 'Debug'
|
||||
official = self._config == 'Official'
|
||||
is_optim = self._config in ('Profile', 'Official')
|
||||
|
||||
# Add user overrides after the other configurations so that they can
|
||||
# refer to them and override them.
|
||||
args.extend(self._settings.items('gn_args'))
|
||||
return args
|
||||
args.append(('target_os', '"ios"'))
|
||||
args.append(('is_debug', is_debug))
|
||||
|
||||
def Generate(self, gn_path, root_path, out_path):
|
||||
buf = io.StringIO()
|
||||
self.WriteArgsGn(buf)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'args.gn'),
|
||||
buf.getvalue(),
|
||||
overwrite=True)
|
||||
if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1':
|
||||
args.append(('use_system_xcode', False))
|
||||
|
||||
subprocess.check_call(
|
||||
self.GetGnCommand(gn_path, root_path, out_path, True))
|
||||
args.append(('target_cpu', self.TARGET_CPU_VALUES[self._target]))
|
||||
args.append((
|
||||
'target_environment',
|
||||
self.TARGET_ENVIRONMENT_VALUES[self._target]))
|
||||
|
||||
def CreateGnRules(self, gn_path, root_path, out_path):
|
||||
buf = io.StringIO()
|
||||
self.WriteArgsGn(buf)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'args.gn'),
|
||||
buf.getvalue(),
|
||||
overwrite=True)
|
||||
# If extra arguments are passed to the function, pass them before the
|
||||
# user overrides (if any).
|
||||
if extra_args is not None:
|
||||
args.extend(extra_args)
|
||||
|
||||
buf = io.StringIO()
|
||||
gn_command = self.GetGnCommand(gn_path, root_path, out_path, False)
|
||||
self.WriteBuildNinja(buf, gn_command)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'build.ninja'),
|
||||
buf.getvalue(),
|
||||
overwrite=False)
|
||||
# Add user overrides after the other configurations so that they can
|
||||
# refer to them and override them.
|
||||
args.extend(self._settings.items('gn_args'))
|
||||
return args
|
||||
|
||||
buf = io.StringIO()
|
||||
self.WriteBuildNinjaDeps(buf)
|
||||
WriteToFileIfChanged(os.path.join(out_path, 'build.ninja.d'),
|
||||
buf.getvalue(),
|
||||
overwrite=False)
|
||||
|
||||
def WriteArgsGn(self, stream):
|
||||
stream.write('# This file was generated by setup-gn.py. Do not edit\n')
|
||||
stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n')
|
||||
stream.write('# to configure settings.\n')
|
||||
stream.write('\n')
|
||||
def Generate(self, gn_path, proj_name, root_path, build_dir):
|
||||
self.WriteArgsGn(build_dir, xcode_project_name=proj_name)
|
||||
subprocess.check_call(self.GetGnCommand(
|
||||
gn_path, root_path, build_dir, xcode_project_name=proj_name))
|
||||
|
||||
def CreateGnRules(self, gn_path, root_path, build_dir):
|
||||
gn_command = self.GetGnCommand(gn_path, root_path, build_dir)
|
||||
self.WriteArgsGn(build_dir)
|
||||
self.WriteBuildNinja(gn_command, build_dir)
|
||||
self.WriteBuildNinjaDeps(build_dir)
|
||||
|
||||
def WriteArgsGn(self, build_dir, xcode_project_name=None):
|
||||
with open(os.path.join(build_dir, 'args.gn'), 'w') as stream:
|
||||
stream.write('# This file was generated by setup-gn.py. Do not edit\n')
|
||||
stream.write('# but instead use ~/.setup-gn or $repo/.setup-gn files\n')
|
||||
stream.write('# to configure settings.\n')
|
||||
stream.write('\n')
|
||||
|
||||
if self._target != 'maccatalyst':
|
||||
if self._settings.has_section('$imports$'):
|
||||
for import_rule in self._settings.values('$imports$'):
|
||||
stream.write('import("%s")\n' % import_rule)
|
||||
stream.write('\n')
|
||||
for import_rule in self._settings.values('$imports$'):
|
||||
stream.write('import("%s")\n' % import_rule)
|
||||
stream.write('\n')
|
||||
|
||||
gn_args = self._GetGnArgs()
|
||||
for name, value in gn_args:
|
||||
if isinstance(value, bool):
|
||||
stream.write('%s = %s\n' % (name, str(value).lower()))
|
||||
elif isinstance(value, list):
|
||||
stream.write('%s = [%s' %
|
||||
(name, '\n' if len(value) > 1 else ''))
|
||||
if len(value) == 1:
|
||||
prefix = ' '
|
||||
suffix = ' '
|
||||
else:
|
||||
prefix = ' '
|
||||
suffix = ',\n'
|
||||
for item in value:
|
||||
if isinstance(item, bool):
|
||||
stream.write('%s%s%s' %
|
||||
(prefix, str(item).lower(), suffix))
|
||||
else:
|
||||
stream.write('%s%s%s' % (prefix, item, suffix))
|
||||
stream.write(']\n')
|
||||
gn_args = self._GetGnArgs()
|
||||
|
||||
for name, value in gn_args:
|
||||
if isinstance(value, bool):
|
||||
stream.write('%s = %s\n' % (name, str(value).lower()))
|
||||
elif isinstance(value, list):
|
||||
stream.write('%s = [%s' % (name, '\n' if len(value) > 1 else ''))
|
||||
if len(value) == 1:
|
||||
prefix = ' '
|
||||
suffix = ' '
|
||||
else:
|
||||
prefix = ' '
|
||||
suffix = ',\n'
|
||||
for item in value:
|
||||
if isinstance(item, bool):
|
||||
stream.write('%s%s%s' % (prefix, str(item).lower(), suffix))
|
||||
else:
|
||||
stream.write('%s = %s\n' % (name, value))
|
||||
|
||||
def WriteBuildNinja(self, stream, gn_command):
|
||||
stream.write('rule gn\n')
|
||||
stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command))
|
||||
stream.write(' description = Regenerating ninja files\n')
|
||||
stream.write('\n')
|
||||
stream.write('build build.ninja: gn\n')
|
||||
stream.write(' generator = 1\n')
|
||||
stream.write(' depfile = build.ninja.d\n')
|
||||
|
||||
def WriteBuildNinjaDeps(self, stream):
|
||||
stream.write('build.ninja: nonexistant_file.gn\n')
|
||||
|
||||
def GetGnCommand(self, gn_path, src_path, out_path, generate_xcode_project):
|
||||
gn_command = [gn_path, '--root=%s' % os.path.realpath(src_path), '-q']
|
||||
if generate_xcode_project:
|
||||
gn_command.append('--ide=xcode')
|
||||
gn_command.append('--ninja-executable=autoninja')
|
||||
if self._settings.has_section('filters'):
|
||||
target_filters = self._settings.values('filters')
|
||||
if target_filters:
|
||||
gn_command.append('--filters=%s' % ';'.join(target_filters))
|
||||
stream.write('%s%s%s' % (prefix, item, suffix))
|
||||
stream.write(']\n')
|
||||
else:
|
||||
gn_command.append('--check')
|
||||
gn_command.append('gen')
|
||||
gn_command.append('//%s' % os.path.relpath(os.path.abspath(out_path),
|
||||
os.path.abspath(src_path)))
|
||||
return gn_command
|
||||
# ConfigParser removes quote around empty string which confuse
|
||||
# `gn gen` so restore them.
|
||||
if not value:
|
||||
value = '""'
|
||||
stream.write('%s = %s\n' % (name, value))
|
||||
|
||||
def WriteBuildNinja(self, gn_command, build_dir):
|
||||
with open(os.path.join(build_dir, 'build.ninja'), 'w') as stream:
|
||||
stream.write('ninja_required_version = 1.7.2\n')
|
||||
stream.write('\n')
|
||||
stream.write('rule gn\n')
|
||||
stream.write(' command = %s\n' % NinjaEscapeCommand(gn_command))
|
||||
stream.write(' description = Regenerating ninja files\n')
|
||||
stream.write('\n')
|
||||
stream.write('build build.ninja: gn\n')
|
||||
stream.write(' generator = 1\n')
|
||||
stream.write(' depfile = build.ninja.d\n')
|
||||
|
||||
def WriteToFileIfChanged(filename, content, overwrite):
|
||||
'''Write |content| to |filename| if different. If |overwrite| is False
|
||||
and the file already exists it is left untouched.'''
|
||||
if os.path.exists(filename):
|
||||
if not overwrite:
|
||||
return
|
||||
with open(filename) as file:
|
||||
if file.read() == content:
|
||||
return
|
||||
if not os.path.isdir(os.path.dirname(filename)):
|
||||
os.makedirs(os.path.dirname(filename))
|
||||
with open(filename, 'w') as file:
|
||||
file.write(content)
|
||||
def WriteBuildNinjaDeps(self, build_dir):
|
||||
with open(os.path.join(build_dir, 'build.ninja.d'), 'w') as stream:
|
||||
stream.write('build.ninja: nonexistant_file.gn\n')
|
||||
|
||||
def GetGnCommand(self, gn_path, src_path, out_path, xcode_project_name=None):
|
||||
gn_command = [ gn_path, '--root=%s' % os.path.realpath(src_path), '-q' ]
|
||||
if xcode_project_name is not None:
|
||||
gn_command.append('--ide=xcode')
|
||||
gn_command.append('--ninja-executable=autoninja')
|
||||
gn_command.append('--xcode-build-system=new')
|
||||
gn_command.append('--xcode-project=%s' % xcode_project_name)
|
||||
if self._settings.has_section('filters'):
|
||||
target_filters = self._settings.values('filters')
|
||||
if target_filters:
|
||||
gn_command.append('--filters=%s' % ';'.join(target_filters))
|
||||
else:
|
||||
gn_command.append('--check')
|
||||
gn_command.append('gen')
|
||||
gn_command.append('//%s' %
|
||||
os.path.relpath(os.path.abspath(out_path), os.path.abspath(src_path)))
|
||||
return gn_command
|
||||
|
||||
|
||||
def NinjaNeedEscape(arg):
|
||||
'''Returns True if |arg| needs to be escaped when written to .ninja file.'''
|
||||
return ':' in arg or '*' in arg or ';' in arg
|
||||
'''Returns True if |arg| needs to be escaped when written to .ninja file.'''
|
||||
return ':' in arg or '*' in arg or ';' in arg
|
||||
|
||||
|
||||
def NinjaEscapeCommand(command):
|
||||
'''Escapes |command| in order to write it to .ninja file.'''
|
||||
result = []
|
||||
for arg in command:
|
||||
if NinjaNeedEscape(arg):
|
||||
arg = arg.replace(':', '$:')
|
||||
arg = arg.replace(';', '\\;')
|
||||
arg = arg.replace('*', '\\*')
|
||||
else:
|
||||
result.append(arg)
|
||||
return ' '.join(result)
|
||||
'''Escapes |command| in order to write it to .ninja file.'''
|
||||
result = []
|
||||
for arg in command:
|
||||
if NinjaNeedEscape(arg):
|
||||
arg = arg.replace(':', '$:')
|
||||
arg = arg.replace(';', '\\;')
|
||||
arg = arg.replace('*', '\\*')
|
||||
else:
|
||||
result.append(arg)
|
||||
return ' '.join(result)
|
||||
|
||||
|
||||
def FindGn():
|
||||
'''Returns absolute path to gn binary looking at the PATH env variable.'''
|
||||
for path in os.environ['PATH'].split(os.path.pathsep):
|
||||
gn_path = os.path.join(path, 'gn')
|
||||
if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK):
|
||||
return gn_path
|
||||
return None
|
||||
'''Returns absolute path to gn binary looking at the PATH env variable.'''
|
||||
for path in os.environ['PATH'].split(os.path.pathsep):
|
||||
gn_path = os.path.join(path, 'gn')
|
||||
if os.path.isfile(gn_path) and os.access(gn_path, os.X_OK):
|
||||
return gn_path
|
||||
return None
|
||||
|
||||
|
||||
def GenerateXcodeProject(gn_path, root_dir, out_dir, settings):
|
||||
'''Convert GN generated Xcode project into multi-configuration Xcode
|
||||
project.'''
|
||||
def GenerateXcodeProject(gn_path, root_dir, proj_name, out_dir, settings):
|
||||
'''Generate Xcode project with Xcode and convert to multi-configurations.'''
|
||||
prefix = os.path.abspath(os.path.join(out_dir, '_temp'))
|
||||
temp_path = tempfile.mkdtemp(prefix=prefix)
|
||||
try:
|
||||
generator = GnGenerator(settings, 'Debug', 'iphonesimulator')
|
||||
generator.Generate(gn_path, proj_name, root_dir, temp_path)
|
||||
convert_gn_xcodeproj.ConvertGnXcodeProject(
|
||||
root_dir,
|
||||
'%s.xcodeproj' % proj_name,
|
||||
os.path.join(temp_path),
|
||||
os.path.join(out_dir, 'build'),
|
||||
SUPPORTED_CONFIGS)
|
||||
finally:
|
||||
if os.path.exists(temp_path):
|
||||
shutil.rmtree(temp_path)
|
||||
|
||||
temp_path = tempfile.mkdtemp(
|
||||
prefix=os.path.abspath(os.path.join(out_dir, '_temp')))
|
||||
try:
|
||||
generator = GnGenerator(settings, 'Debug', 'iphonesimulator')
|
||||
generator.Generate(gn_path, root_dir, temp_path)
|
||||
convert_gn_xcodeproj.ConvertGnXcodeProject(
|
||||
root_dir, os.path.join(temp_path), os.path.join(out_dir, 'build'),
|
||||
SUPPORTED_CONFIGS)
|
||||
finally:
|
||||
if os.path.exists(temp_path):
|
||||
shutil.rmtree(temp_path)
|
||||
def CreateLLDBInitFile(root_dir, out_dir, settings):
|
||||
'''
|
||||
Generate an .lldbinit file for the project that load the script that fixes
|
||||
the mapping of source files (see docs/ios/build_instructions.md#debugging).
|
||||
'''
|
||||
with open(os.path.join(out_dir, 'build', '.lldbinit'), 'w') as lldbinit:
|
||||
lldb_script_dir = os.path.join(os.path.abspath(root_dir), 'tools', 'lldb')
|
||||
lldbinit.write('script sys.path[:0] = [\'%s\']\n' % lldb_script_dir)
|
||||
lldbinit.write('script import lldbinit\n')
|
||||
|
||||
workspace_name = settings.getstring(
|
||||
'gn_args',
|
||||
'ios_internal_citc_workspace_name')
|
||||
|
||||
if workspace_name != '':
|
||||
username = os.environ['USER']
|
||||
for shortname in ('googlemac', 'third_party', 'blaze-out'):
|
||||
lldbinit.write('settings append target.source-map %s %s\n' % (
|
||||
shortname,
|
||||
'/google/src/cloud/%s/%s/google3/%s' % (
|
||||
username, workspace_name, shortname)))
|
||||
|
||||
# Append the content of //ios/build/tools/lldbinit.defaults if it exists.
|
||||
tools_dir = os.path.join(root_dir, 'ios', 'build', 'tools')
|
||||
defaults_lldbinit_path = os.path.join(tools_dir, 'lldbinit.defaults')
|
||||
if os.path.isfile(defaults_lldbinit_path):
|
||||
with open(defaults_lldbinit_path) as defaults_lldbinit:
|
||||
for line in defaults_lldbinit:
|
||||
lldbinit.write(line)
|
||||
|
||||
# Append the content of ~/.lldbinit if it exists. Line that look like they
|
||||
# are trying to configure source mapping are skipped as they probably date
|
||||
# back from when setup-gn.py was not generating an .lldbinit file.
|
||||
global_lldbinit_path = os.path.join(os.environ['HOME'], '.lldbinit')
|
||||
if os.path.isfile(global_lldbinit_path):
|
||||
with open(global_lldbinit_path) as global_lldbinit:
|
||||
for line in global_lldbinit:
|
||||
if any(pattern.match(line) for pattern in LLDBINIT_SKIP_PATTERNS):
|
||||
continue
|
||||
lldbinit.write(line)
|
||||
|
||||
|
||||
def GenerateGnBuildRules(gn_path, root_dir, out_dir, settings):
|
||||
'''Generates all template configurations for gn.'''
|
||||
for config in SUPPORTED_CONFIGS:
|
||||
for target in SUPPORTED_TARGETS:
|
||||
build_dir = os.path.join(out_dir, '%s-%s' % (config, target))
|
||||
generator = GnGenerator(settings, config, target)
|
||||
generator.CreateGnRules(gn_path, root_dir, build_dir)
|
||||
'''Generates all template configurations for gn.'''
|
||||
for config in SUPPORTED_CONFIGS:
|
||||
for target in SUPPORTED_TARGETS:
|
||||
build_dir = os.path.join(out_dir, '%s-%s' % (config, target))
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
generator = GnGenerator(settings, config, target)
|
||||
generator.CreateGnRules(gn_path, root_dir, build_dir)
|
||||
|
||||
|
||||
def Main(args):
|
||||
default_root = os.path.normpath(
|
||||
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
default_root = os.path.normpath(os.path.join(
|
||||
os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate build directories for use with gn.')
|
||||
parser.add_argument(
|
||||
'root',
|
||||
default=default_root,
|
||||
nargs='?',
|
||||
help='root directory where to generate multiple out configurations')
|
||||
parser.add_argument('--import',
|
||||
action='append',
|
||||
dest='import_rules',
|
||||
default=[],
|
||||
help='path to file defining default gn variables')
|
||||
parser.add_argument('--gn-path',
|
||||
default=None,
|
||||
help='path to gn binary (default: look up in $PATH)')
|
||||
parser.add_argument(
|
||||
'--build-dir',
|
||||
default='out',
|
||||
help='path where the build should be created (default: %(default)s)')
|
||||
args = parser.parse_args(args)
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate build directories for use with gn.')
|
||||
parser.add_argument(
|
||||
'root', default=default_root, nargs='?',
|
||||
help='root directory where to generate multiple out configurations')
|
||||
parser.add_argument(
|
||||
'--import', action='append', dest='import_rules', default=[],
|
||||
help='path to file defining default gn variables')
|
||||
parser.add_argument(
|
||||
'--gn-path', default=None,
|
||||
help='path to gn binary (default: look up in $PATH)')
|
||||
parser.add_argument(
|
||||
'--build-dir', default='out',
|
||||
help='path where the build should be created (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--config-path', default=os.path.expanduser('~/.setup-gn'),
|
||||
help='path to the user config file (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--system-config-path', default=os.path.splitext(__file__)[0] + '.config',
|
||||
help='path to the default config file (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--project-name', default='all', dest='proj_name',
|
||||
help='name of the generated Xcode project (default: %(default)s)')
|
||||
parser.add_argument(
|
||||
'--no-xcode-project', action='store_true', default=False,
|
||||
help='do not generate the build directory with XCode project')
|
||||
args = parser.parse_args(args)
|
||||
|
||||
# Load configuration (first global and then any user overrides).
|
||||
settings = ConfigParserWithStringInterpolation()
|
||||
settings.read([
|
||||
os.path.splitext(__file__)[0] + '.config',
|
||||
os.path.expanduser('~/.setup-gn'),
|
||||
])
|
||||
# Load configuration (first global and then any user overrides).
|
||||
settings = ConfigParserWithStringInterpolation()
|
||||
settings.read([
|
||||
args.system_config_path,
|
||||
args.config_path,
|
||||
])
|
||||
|
||||
# Add private sections corresponding to --import argument.
|
||||
if args.import_rules:
|
||||
settings.add_section('$imports$')
|
||||
for i, import_rule in enumerate(args.import_rules):
|
||||
if not import_rule.startswith('//'):
|
||||
import_rule = '//%s' % os.path.relpath(
|
||||
os.path.abspath(import_rule), os.path.abspath(args.root))
|
||||
settings.set('$imports$', '$rule%d$' % i, import_rule)
|
||||
# Add private sections corresponding to --import argument.
|
||||
if args.import_rules:
|
||||
settings.add_section('$imports$')
|
||||
for i, import_rule in enumerate(args.import_rules):
|
||||
if not import_rule.startswith('//'):
|
||||
import_rule = '//%s' % os.path.relpath(
|
||||
os.path.abspath(import_rule), os.path.abspath(args.root))
|
||||
settings.set('$imports$', '$rule%d$' % i, import_rule)
|
||||
|
||||
# Validate settings.
|
||||
if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'):
|
||||
sys.stderr.write('ERROR: invalid value for build.arch: %s\n' %
|
||||
settings.getstring('build', 'arch'))
|
||||
sys.exit(1)
|
||||
# Validate settings.
|
||||
if settings.getstring('build', 'arch') not in ('64-bit', '32-bit', 'fat'):
|
||||
sys.stderr.write('ERROR: invalid value for build.arch: %s\n' %
|
||||
settings.getstring('build', 'arch'))
|
||||
sys.exit(1)
|
||||
|
||||
# Find path to gn binary either from command-line or in PATH.
|
||||
if args.gn_path:
|
||||
gn_path = args.gn_path
|
||||
else:
|
||||
gn_path = FindGn()
|
||||
if gn_path is None:
|
||||
sys.stderr.write('ERROR: cannot find gn in PATH\n')
|
||||
sys.exit(1)
|
||||
# Find path to gn binary either from command-line or in PATH.
|
||||
if args.gn_path:
|
||||
gn_path = args.gn_path
|
||||
else:
|
||||
gn_path = FindGn()
|
||||
if gn_path is None:
|
||||
sys.stderr.write('ERROR: cannot find gn in PATH\n')
|
||||
sys.exit(1)
|
||||
|
||||
out_dir = os.path.join(args.root, args.build_dir)
|
||||
if not os.path.isdir(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
out_dir = os.path.join(args.root, args.build_dir)
|
||||
if not os.path.isdir(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
GenerateXcodeProject(gn_path, args.root, out_dir, settings)
|
||||
GenerateGnBuildRules(gn_path, args.root, out_dir, settings)
|
||||
if not args.no_xcode_project:
|
||||
GenerateXcodeProject(gn_path, args.root, args.proj_name, out_dir, settings)
|
||||
CreateLLDBInitFile(args.root, out_dir, settings)
|
||||
GenerateGnBuildRules(gn_path, args.root, out_dir, settings)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(Main(sys.argv[1:]))
|
||||
sys.exit(Main(sys.argv[1:]))
|
||||
|
10
build/ios/xcodescheme-testable.template
Normal file
10
build/ios/xcodescheme-testable.template
Normal file
@ -0,0 +1,10 @@
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
80
build/ios/xcodescheme.template
Normal file
80
build/ios/xcodescheme.template
Normal file
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1220"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "@{LLDBINIT_PATH}"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>@{TESTABLES}
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "@{LLDBINIT_PATH}"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "@{BLUEPRINT_IDENTIFIER}"
|
||||
BuildableName = "@{BUILDABLE_NAME}"
|
||||
BlueprintName = "@{BLUEPRINT_NAME}"
|
||||
ReferencedContainer = "container:@{PROJECT_NAME}">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Official"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -104,6 +104,17 @@ GetProcessSnapshotMinidumpFromSinglePending() {
|
||||
return process_snapshot;
|
||||
}
|
||||
|
||||
UIWindow* GetAnyWindow() {
|
||||
#if defined(__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0
|
||||
if (@available(iOS 15.0, *)) {
|
||||
UIWindowScene* scene = reinterpret_cast<UIWindowScene*>(
|
||||
[UIApplication sharedApplication].connectedScenes.anyObject);
|
||||
return scene.keyWindow;
|
||||
}
|
||||
#endif
|
||||
return [UIApplication sharedApplication].windows[0];
|
||||
}
|
||||
|
||||
[[clang::optnone]] void recurse(int counter) {
|
||||
// Fill up the stack faster.
|
||||
int arr[1024];
|
||||
@ -355,7 +366,7 @@ GetProcessSnapshotMinidumpFromSinglePending() {
|
||||
// crash, so dispatch this out of the sinkhole.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIView* unattachedView = [[UIView alloc] init];
|
||||
UIWindow* window = [UIApplication sharedApplication].windows[0];
|
||||
UIWindow* window = GetAnyWindow();
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[window.rootViewController.view.bottomAnchor
|
||||
constraintEqualToAnchor:unattachedView.bottomAnchor],
|
||||
|
Loading…
x
Reference in New Issue
Block a user