Commit 61f7b960 authored by Sylvain Defresne's avatar Sylvain Defresne Committed by Commit Bot

[ios] Update packaging rules to support "catalyst" environment

App built for "catalyst" environment are regular macOS app (linked
with some support libraries). Update the build rules to generate a
macOS app bundle when environment is "catalyst".

Update the codesign.py and write_framework_modulemap.py script to
use the correct bundle structure.

Bug: 1138425
Change-Id: I135068a92101b755f588ca25ae9420660ea4a66c
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2498527
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: default avatarRohit Rao <rohitrao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821849}
parent befbdb33
...@@ -63,25 +63,85 @@ def LoadPlistFile(plist_path): ...@@ -63,25 +63,85 @@ def LoadPlistFile(plist_path):
['xcrun', 'plutil', '-convert', 'xml1', '-o', '-', plist_path])) ['xcrun', 'plutil', '-convert', 'xml1', '-o', '-', plist_path]))
def CreateSymlink(value, location):
"""Creates symlink with value at location if the target exists."""
target = os.path.join(os.path.dirname(location), value)
if os.path.exists(location):
os.unlink(location)
os.symlink(value, location)
class Bundle(object): class Bundle(object):
"""Wraps a bundle.""" """Wraps a bundle."""
def __init__(self, bundle_path): def __init__(self, bundle_path, platform):
"""Initializes the Bundle object with data from bundle Info.plist file.""" """Initializes the Bundle object with data from bundle Info.plist file."""
self._path = bundle_path self._path = bundle_path
self._data = LoadPlistFile(os.path.join(self._path, 'Info.plist')) self._kind = Bundle.Kind(platform, os.path.splitext(bundle_path)[-1])
self._data = None
def Load(self):
self._data = LoadPlistFile(self.info_plist_path)
@staticmethod
def Kind(platform, extension):
if platform == 'iphonesimulator' or platform == 'iphoneos':
return 'ios'
if platform == 'macosx':
if extension == '.framework':
return 'mac_framework'
return 'mac'
raise ValueError('unknown bundle type %s for %s' % (extension, platform))
@property
def kind(self):
return self._kind
@property @property
def path(self): def path(self):
return self._path return self._path
@property
def contents_dir(self):
if self._kind == 'mac':
return os.path.join(self.path, 'Contents')
if self._kind == 'mac_framework':
return os.path.join(self.path, 'Versions/A')
return self.path
@property
def executable_dir(self):
if self._kind == 'mac':
return os.path.join(self.contents_dir, 'MacOS')
return self.contents_dir
@property
def resources_dir(self):
if self._kind == 'mac' or self._kind == 'mac_framework':
return os.path.join(self.contents_dir, 'Resources')
return self.path
@property
def info_plist_path(self):
if self._kind == 'mac_framework':
return os.path.join(self.resources_dir, 'Info.plist')
return os.path.join(self.contents_dir, 'Info.plist')
@property
def signature_dir(self):
return os.path.join(self.contents_dir, '_CodeSignature')
@property @property
def identifier(self): def identifier(self):
return self._data['CFBundleIdentifier'] return self._data['CFBundleIdentifier']
@property
def binary_name(self):
return self._data['CFBundleExecutable']
@property @property
def binary_path(self): def binary_path(self):
return os.path.join(self._path, self._data['CFBundleExecutable']) return os.path.join(self.executable_dir, self.binary_name)
def Validate(self, expected_mappings): def Validate(self, expected_mappings):
"""Checks that keys in the bundle have the expected value. """Checks that keys in the bundle have the expected value.
...@@ -311,19 +371,18 @@ def GenerateEntitlements(path, provisioning_profile, bundle_identifier): ...@@ -311,19 +371,18 @@ def GenerateEntitlements(path, provisioning_profile, bundle_identifier):
return entitlements return entitlements
def GenerateBundleInfoPlist(bundle_path, plist_compiler, partial_plist): def GenerateBundleInfoPlist(bundle, plist_compiler, partial_plist):
"""Generates the bundle Info.plist for a list of partial .plist files. """Generates the bundle Info.plist for a list of partial .plist files.
Args: Args:
bundle_path: path to the bundle bundle: a Bundle instance
plist_compiler: string, path to the Info.plist compiler plist_compiler: string, path to the Info.plist compiler
partial_plist: list of path to partial .plist files to merge partial_plist: list of path to partial .plist files to merge
""" """
# Filter empty partial .plist files (this happens if an application # Filter empty partial .plist files (this happens if an application
# does not include need to compile any asset catalog, in which case # does not compile any asset catalog, in which case the partial .plist
# the partial .plist file from the asset catalog compilation step is # file from the asset catalog compilation step is just a stamp file).
# just a stamp file).
filtered_partial_plist = [] filtered_partial_plist = []
for plist in partial_plist: for plist in partial_plist:
plist_size = os.stat(plist).st_size plist_size = os.stat(plist).st_size
...@@ -332,8 +391,13 @@ def GenerateBundleInfoPlist(bundle_path, plist_compiler, partial_plist): ...@@ -332,8 +391,13 @@ def GenerateBundleInfoPlist(bundle_path, plist_compiler, partial_plist):
# Invoke the plist_compiler script. It needs to be a python script. # Invoke the plist_compiler script. It needs to be a python script.
subprocess.check_call([ subprocess.check_call([
'python', plist_compiler, 'merge', '-f', 'binary1', 'python',
'-o', os.path.join(bundle_path, 'Info.plist'), plist_compiler,
'merge',
'-f',
'binary1',
'-o',
bundle.info_plist_path,
] + filtered_partial_plist) ] + filtered_partial_plist)
...@@ -392,13 +456,16 @@ class CodeSignBundleAction(Action): ...@@ -392,13 +456,16 @@ class CodeSignBundleAction(Action):
if not args.identity: if not args.identity:
args.identity = '-' args.identity = '-'
bundle = Bundle(args.path, args.platform)
if args.partial_info_plist: if args.partial_info_plist:
GenerateBundleInfoPlist( GenerateBundleInfoPlist(bundle, args.plist_compiler_path,
args.path, args.partial_info_plist)
args.plist_compiler_path,
args.partial_info_plist)
bundle = Bundle(args.path) # The bundle Info.plist may have been updated by GenerateBundleInfoPlist()
# above. Load the bundle information from Info.plist after the modification
# have been written to disk.
bundle.Load()
# According to Apple documentation, the application binary must be the same # According to Apple documentation, the application binary must be the same
# as the bundle name without the .app suffix. See crbug.com/740476 for more # as the bundle name without the .app suffix. See crbug.com/740476 for more
...@@ -435,19 +502,37 @@ class CodeSignBundleAction(Action): ...@@ -435,19 +502,37 @@ class CodeSignBundleAction(Action):
os.unlink(embedded_provisioning_profile) os.unlink(embedded_provisioning_profile)
# Delete existing code signature. # Delete existing code signature.
signature_file = os.path.join(args.path, '_CodeSignature', 'CodeResources') if os.path.exists(bundle.signature_dir):
if os.path.isfile(signature_file): shutil.rmtree(bundle.signature_dir)
shutil.rmtree(os.path.dirname(signature_file))
# Install system frameworks if requested. # Install system frameworks if requested.
for framework_path in args.frameworks: for framework_path in args.frameworks:
InstallSystemFramework(framework_path, args.path, args) InstallSystemFramework(framework_path, args.path, args)
# Copy main binary into bundle. # Copy main binary into bundle.
if os.path.isfile(bundle.binary_path): if not os.path.isdir(bundle.executable_dir):
os.unlink(bundle.binary_path) os.makedirs(bundle.executable_dir)
shutil.copy(args.binary, bundle.binary_path) shutil.copy(args.binary, bundle.binary_path)
if bundle.kind == 'mac_framework':
# Create Versions/Current -> Versions/A symlink
CreateSymlink('A', os.path.join(bundle.path, 'Versions/Current'))
# Create $binary_name -> Versions/Current/$binary_name symlink
CreateSymlink(os.path.join('Versions/Current', bundle.binary_name),
os.path.join(bundle.path, bundle.binary_name))
# Create optional symlinks.
for name in ('Headers', 'Resources', 'Modules'):
target = os.path.join(bundle.path, 'Versions/A', name)
if os.path.exists(target):
CreateSymlink(os.path.join('Versions/Current', name),
os.path.join(bundle.path, name))
else:
obsolete_path = os.path.join(bundle.path, name)
if os.path.exists(obsolete_path):
os.unlink(obsolete_path)
if args.no_signature: if args.no_signature:
return return
......
...@@ -172,6 +172,11 @@ template("lipo_binary") { ...@@ -172,6 +172,11 @@ template("lipo_binary") {
# entitlements (must generate a single file as output); cannot be # entitlements (must generate a single file as output); cannot be
# defined if entitlements_path is set. # defined if entitlements_path is set.
# #
# has_public_headers:
# (optional) boolean, defaults to false; only meaningful if the bundle
# is a framework bundle; if true, then the frameworks includes public
# headers
#
# disable_entitlements # disable_entitlements
# (optional, defaults to false) boolean, control whether entitlements willi # (optional, defaults to false) boolean, control whether entitlements willi
# be embedded in the application during signature. If false and no # be embedded in the application during signature. If false and no
...@@ -253,6 +258,10 @@ template("create_signed_bundle") { ...@@ -253,6 +258,10 @@ template("create_signed_bundle") {
_enable_embedded_mobileprovision = !invoker.disable_embedded_mobileprovision _enable_embedded_mobileprovision = !invoker.disable_embedded_mobileprovision
} }
if (target_environment == "catalyst") {
_enable_embedded_mobileprovision = false
}
_enable_entitlements = true _enable_entitlements = true
if (defined(invoker.disable_entitlements)) { if (defined(invoker.disable_entitlements)) {
_enable_entitlements = !invoker.disable_entitlements _enable_entitlements = !invoker.disable_entitlements
...@@ -311,9 +320,21 @@ template("create_signed_bundle") { ...@@ -311,9 +320,21 @@ template("create_signed_bundle") {
]) ])
bundle_root_dir = "$_bundle_gen_dir/$_output_name$_bundle_extension" bundle_root_dir = "$_bundle_gen_dir/$_output_name$_bundle_extension"
bundle_contents_dir = bundle_root_dir if (target_environment == "simulator" || target_environment == "device") {
bundle_resources_dir = bundle_contents_dir bundle_contents_dir = bundle_root_dir
bundle_executable_dir = bundle_contents_dir bundle_resources_dir = bundle_contents_dir
bundle_executable_dir = bundle_contents_dir
} else if (target_environment == "catalyst") {
if (_bundle_extension != ".framework") {
bundle_contents_dir = "$bundle_root_dir/Contents"
bundle_resources_dir = "$bundle_contents_dir/Resources"
bundle_executable_dir = "$bundle_contents_dir/MacOS"
} else {
bundle_contents_dir = "$bundle_root_dir/Versions/A"
bundle_resources_dir = "$bundle_contents_dir/Resources"
bundle_executable_dir = bundle_contents_dir
}
}
if (!defined(public_deps)) { if (!defined(public_deps)) {
public_deps = [] public_deps = []
...@@ -359,7 +380,7 @@ template("create_signed_bundle") { ...@@ -359,7 +380,7 @@ template("create_signed_bundle") {
} }
code_signing_sources += [ _entitlements_path ] code_signing_sources += [ _entitlements_path ]
} }
code_signing_outputs = [ "$bundle_contents_dir/$_output_name" ] code_signing_outputs = [ "$bundle_executable_dir/$_output_name" ]
if (_enable_code_signing) { if (_enable_code_signing) {
code_signing_outputs += code_signing_outputs +=
[ "$bundle_contents_dir/_CodeSignature/CodeResources" ] [ "$bundle_contents_dir/_CodeSignature/CodeResources" ]
...@@ -369,6 +390,23 @@ template("create_signed_bundle") { ...@@ -369,6 +390,23 @@ template("create_signed_bundle") {
code_signing_outputs += code_signing_outputs +=
[ "$bundle_contents_dir/embedded.mobileprovision" ] [ "$bundle_contents_dir/embedded.mobileprovision" ]
} }
if (_bundle_extension == ".framework") {
if (target_environment == "catalyst") {
code_signing_outputs += [
"$bundle_root_dir/Versions/Current",
"$bundle_root_dir/$_output_name",
]
if (defined(invoker.has_public_headers) && invoker.has_public_headers) {
code_signing_outputs += [
"$bundle_root_dir/Headers",
"$bundle_root_dir/Modules",
]
}
} else {
not_needed(invoker, [ "has_public_headers" ])
}
}
if (defined(invoker.extra_system_frameworks)) { if (defined(invoker.extra_system_frameworks)) {
foreach(_framework, invoker.extra_system_frameworks) { foreach(_framework, invoker.extra_system_frameworks) {
...@@ -417,7 +455,12 @@ template("create_signed_bundle") { ...@@ -417,7 +455,12 @@ template("create_signed_bundle") {
code_signing_sources += _partial_info_plists code_signing_sources += _partial_info_plists
code_signing_sources += [ _plist_compiler_path ] code_signing_sources += [ _plist_compiler_path ]
code_signing_outputs += [ "$bundle_contents_dir/Info.plist" ] if (target_environment != "catalyst" ||
_bundle_extension != ".framework") {
code_signing_outputs += [ "$bundle_contents_dir/Info.plist" ]
} else {
code_signing_outputs += [ "$bundle_resources_dir/Info.plist" ]
}
code_signing_args += code_signing_args +=
[ "-P=" + rebase_path(_plist_compiler_path, root_build_dir) ] [ "-P=" + rebase_path(_plist_compiler_path, root_build_dir) ]
...@@ -1362,7 +1405,13 @@ template("ios_framework_bundle") { ...@@ -1362,7 +1405,13 @@ template("ios_framework_bundle") {
} else { } else {
if (_has_public_headers) { if (_has_public_headers) {
_public_headers = invoker.public_headers _public_headers = invoker.public_headers
_framework_root = "$root_out_dir/$_output_name.framework"
_framework_root_dir = "$root_out_dir/$_output_name.framework"
if (target_environment == "simulator" || target_environment == "device") {
_frameworks_content_dir = _framework_root_dir
} else if (target_environment == "catalyst") {
_frameworks_content_dir = "$_framework_root_dir/Versions/A"
}
_compile_headers_map_target = _target_name + "_compile_headers_map" _compile_headers_map_target = _target_name + "_compile_headers_map"
action(_compile_headers_map_target) { action(_compile_headers_map_target) {
...@@ -1389,7 +1438,7 @@ template("ios_framework_bundle") { ...@@ -1389,7 +1438,7 @@ template("ios_framework_bundle") {
args = [ args = [
rebase_path(_header_map_filename), rebase_path(_header_map_filename),
rebase_path(_framework_root, root_build_dir), rebase_path(_framework_root_dir, root_build_dir),
] + rebase_path(sources, root_build_dir) ] + rebase_path(sources, root_build_dir)
} }
...@@ -1397,8 +1446,11 @@ template("ios_framework_bundle") { ...@@ -1397,8 +1446,11 @@ template("ios_framework_bundle") {
action(_create_module_map_target) { action(_create_module_map_target) {
visibility = [ ":$_framework_headers_target" ] visibility = [ ":$_framework_headers_target" ]
script = "//build/config/ios/write_framework_modulemap.py" script = "//build/config/ios/write_framework_modulemap.py"
outputs = [ "$_framework_root/Modules/module.modulemap" ] outputs = [ "$_frameworks_content_dir/Modules/module.modulemap" ]
args = [ rebase_path("$_framework_root", root_build_dir) ] args = [
_output_name,
rebase_path("$_frameworks_content_dir/Modules", root_build_dir),
]
} }
_copy_public_headers_target = _target_name + "_copy_public_headers" _copy_public_headers_target = _target_name + "_copy_public_headers"
...@@ -1410,7 +1462,7 @@ template("ios_framework_bundle") { ...@@ -1410,7 +1462,7 @@ template("ios_framework_bundle") {
]) ])
visibility = [ ":$_framework_headers_target" ] visibility = [ ":$_framework_headers_target" ]
sources = _public_headers sources = _public_headers
outputs = [ "$_framework_root/Headers/{{source_file_part}}" ] outputs = [ "$_frameworks_content_dir/Headers/{{source_file_part}}" ]
# Do not use forward_variables_from for "public_deps" as # Do not use forward_variables_from for "public_deps" as
# we do not want to forward those dependencies. # we do not want to forward those dependencies.
...@@ -1462,8 +1514,13 @@ template("ios_framework_bundle") { ...@@ -1462,8 +1514,13 @@ template("ios_framework_bundle") {
visibility = [ ":${_target_name}_signed_bundle" ] visibility = [ ":${_target_name}_signed_bundle" ]
forward_variables_from(invoker, [ "testonly" ]) forward_variables_from(invoker, [ "testonly" ])
sources = get_target_outputs(":$_info_plist_target") sources = get_target_outputs(":$_info_plist_target")
outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
public_deps = [ ":$_info_plist_target" ] public_deps = [ ":$_info_plist_target" ]
if (target_environment != "catalyst") {
outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
} else {
outputs = [ "{{bundle_resources_dir}}/Info.plist" ]
}
} }
create_signed_bundle(_target_name + "_signed_bundle") { create_signed_bundle(_target_name + "_signed_bundle") {
...@@ -1487,6 +1544,8 @@ template("ios_framework_bundle") { ...@@ -1487,6 +1544,8 @@ template("ios_framework_bundle") {
bundle_binary_target = ":$_lipo_shared_library_target" bundle_binary_target = ":$_lipo_shared_library_target"
bundle_binary_output = _output_name bundle_binary_output = _output_name
has_public_headers = _has_public_headers
# Framework do not have entitlements nor mobileprovision because they use # Framework do not have entitlements nor mobileprovision because they use
# the one from the bundle using them (.app or .appex) as they are just # the one from the bundle using them (.app or .appex) as they are just
# dynamic library with shared code. # dynamic library with shared code.
...@@ -1547,8 +1606,7 @@ template("ios_framework_bundle") { ...@@ -1547,8 +1606,7 @@ template("ios_framework_bundle") {
]) ])
public_deps = [ ":$_target_name" ] public_deps = [ ":$_target_name" ]
sources = [ "$root_out_dir/$_output_name.framework" ] sources = [ "$root_out_dir/$_output_name.framework" ]
outputs = outputs = [ "{{bundle_contents_dir}}/Frameworks/$_output_name.framework" ]
[ "{{bundle_resources_dir}}/Frameworks/$_output_name.framework" ]
} }
} }
} }
......
...@@ -5,22 +5,24 @@ ...@@ -5,22 +5,24 @@
import os import os
import sys import sys
def Main(framework): MODULE_MAP_TEMPLATE = '''\
framework module %(framework_name)s {
umbrella header "%(framework_name)s.h"
export *
module * { export * }
}
'''
def Main(framework_name, modules_dir):
# Find the name of the binary based on the part before the ".framework". # Find the name of the binary based on the part before the ".framework".
binary = os.path.basename(framework).split('.')[0] if not os.path.isdir(modules_dir):
module_path = os.path.join(framework, 'Modules'); os.makedirs(modules_dir)
if not os.path.exists(module_path):
os.mkdir(module_path) with open(os.path.join(modules_dir, 'module.modulemap'), 'w') as module_file:
module_template = 'framework module %s {\n' \ module_file.write(MODULE_MAP_TEMPLATE % {'framework_name': framework_name})
' umbrella header "%s.h"\n' \
'\n' \
' export *\n' \
' module * { export * }\n' \
'}\n' % (binary, binary)
module_file = open(os.path.join(module_path, 'module.modulemap'), 'w')
module_file.write(module_template)
module_file.close()
if __name__ == '__main__': if __name__ == '__main__':
Main(sys.argv[1]) Main(*sys.argv[1:])
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment