blob: 8090cd08decacd345e36f4952ef4e0ea33bbf38e [file]
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
The modification module handles making changes to files in the application
bundle. At minimum, the Info.plists are modified to support Keystone and the
code signing entitlements file is added. In addition, other components might
be modified or renamed to support side-by-side channel installs.
"""
import os.path
from signing import commands, parts
_CF_BUNDLE_DISPLAY_NAME = 'CFBundleDisplayName'
_CF_BUNDLE_EXE = 'CFBundleExecutable'
_CF_BUNDLE_ID = 'CFBundleIdentifier'
_CF_BUNDLE_NAME = 'CFBundleName'
_ENT_APP_ID = 'com.apple.application-identifier'
_ENT_GET_TASK_ALLOW = 'com.apple.security.get-task-allow'
_KS_BRAND_ID = 'KSBrandID'
_KS_CHANNEL_ID = 'KSChannelID'
_KS_PRODUCT_ID = 'KSProductID'
def _should_keep_scheme(scheme, dist):
"""Returns True if the URL scheme should be kept in the Info.plist."""
if scheme not in ('google-chrome', 'chromium'):
return True
if dist.direct_launch_scheme is not None:
return scheme == dist.direct_launch_scheme
return True
def _modify_plists(paths, dist, config):
"""Modifies several plist files in the bundle.
This alters the CFBundleIdentifier if necessary and sets Keystone updater
keys.
Args:
paths: A |model.Paths| object.
dist: The |model.Distribution| for which customization is taking place.
config: The |config.CodeSignConfig| object.
"""
app_plist_path = os.path.join(
paths.work, config.app_dir, 'Contents', 'Info.plist'
)
with commands.PlistContext(app_plist_path, rewrite=True) as app_plist:
if dist.channel_customize:
for alert_app in (
'{} Helper (Alerts).app'.format(config.product),
'{} Helper (Aperitif Alerts).app'.format(config.product),
):
alert_helper_app_path = os.path.join(
paths.work,
config.framework_dir,
'Helpers',
alert_app,
)
alert_helper_plist_path = os.path.join(
alert_helper_app_path, 'Contents', 'Info.plist'
)
with commands.PlistContext(
alert_helper_plist_path, rewrite=True
) as alert_helper_plist:
alert_helper_plist[_CF_BUNDLE_ID] = alert_helper_plist[
_CF_BUNDLE_ID
].replace(
config.base_config.base_bundle_id,
config.base_bundle_id,
)
alert_helper_plist_strings_path = os.path.join(
alert_helper_app_path,
'Contents',
'Resources',
'base.lproj',
'InfoPlist.strings',
)
with commands.PlistContext(
alert_helper_plist_strings_path,
rewrite=True,
binary=True,
) as alert_helper_plist_strings:
alert_helper_plist_strings[_CF_BUNDLE_DISPLAY_NAME] = (
'{} {}'.format(
alert_helper_plist_strings[_CF_BUNDLE_DISPLAY_NAME],
dist.app_name_fragment,
)
)
app_plist[_CF_BUNDLE_DISPLAY_NAME] = '{} {}'.format(
app_plist[_CF_BUNDLE_DISPLAY_NAME], dist.app_name_fragment
)
app_plist[_CF_BUNDLE_EXE] = config.app_product
app_plist[_CF_BUNDLE_ID] = config.base_bundle_id
app_plist[_CF_BUNDLE_NAME] = '{} {}'.format(
app_plist[_CF_BUNDLE_NAME], dist.app_name_fragment
)
app_plist[_KS_PRODUCT_ID] += '.' + dist.channel
# Apply the channel and brand code changes.
if dist.branding_code:
app_plist[_KS_BRAND_ID] = dist.branding_code
elif _KS_BRAND_ID in app_plist:
del app_plist[_KS_BRAND_ID]
base_tag = app_plist.get(_KS_CHANNEL_ID)
base_channel_tag_components = []
if base_tag:
base_channel_tag_components.append(base_tag)
if dist.channel:
base_channel_tag_components.append(dist.channel)
base_channel_tag = '-'.join(base_channel_tag_components)
if base_channel_tag:
app_plist[_KS_CHANNEL_ID] = base_channel_tag
elif _KS_CHANNEL_ID in app_plist:
del app_plist[_KS_CHANNEL_ID]
if dist.product_dirname:
app_plist['CrProductDirName'] = dist.product_dirname
if dist.creator_code:
app_plist['CFBundleSignature'] = dist.creator_code
# See build/apple/tweak_info_plist.py and
# chrome/browser/mac/keystone_glue.mm.
for key in app_plist.keys():
if not key.startswith(_KS_CHANNEL_ID + '-'):
continue
ignore, extra = key.split('-')
app_plist[key] = '{}-{}'.format(base_channel_tag, extra)
# The 'google-chrome' and 'chromium' URI schemes are registered by
# build/apple/tweak_info_plist.py.
# We need to filter them based on the distribution configuration (e.g.
# removing them for side-by-side channels).
url_types = app_plist.get('CFBundleURLTypes')
if url_types:
new_url_types = []
for url_type in url_types:
schemes = url_type.get('CFBundleURLSchemes', [])
new_schemes = []
for scheme in schemes:
if _should_keep_scheme(scheme, dist):
new_schemes.append(scheme)
if new_schemes:
url_type['CFBundleURLSchemes'] = new_schemes
new_url_types.append(url_type)
app_plist['CFBundleURLTypes'] = new_url_types
def _replace_icons(paths, dist, config):
"""Replaces icon assets in the bundle with the channel-customized versions.
Args:
paths: A |model.Paths| object.
dist: The |model.Distribution| for which customization is taking place.
config: The |config.CodeSignConfig| object.
"""
assert dist.channel_customize
packaging_dir = paths.packaging_dir(config)
resources_dir = os.path.join(paths.work, config.resources_dir)
new_asset_catalog = os.path.join(
packaging_dir, 'Assets_{}.car'.format(dist.channel)
)
new_app_icon = os.path.join(
packaging_dir, 'app_{}.icns'.format(dist.channel)
)
commands.copy_files(
new_asset_catalog, os.path.join(resources_dir, 'Assets.car')
)
commands.copy_files(new_app_icon, os.path.join(resources_dir, 'app.icns'))
# Also update the icon in the Alert Helper app.
for alert_app in (
'{} Helper (Alerts).app'.format(config.product),
'{} Helper (Aperitif Alerts).app'.format(config.product),
):
alert_helper_resources_dir = os.path.join(
paths.work,
config.framework_dir,
'Helpers',
alert_app,
'Contents',
'Resources',
)
commands.copy_files(
new_asset_catalog,
os.path.join(alert_helper_resources_dir, 'Assets.car'),
)
commands.copy_files(
new_app_icon, os.path.join(alert_helper_resources_dir, 'app.icns')
)
def _rename_enterprise_manifest(paths, dist, config):
"""Modifies and renames the enterprise policy manifest files for channel-
customized versions.
Args:
paths: A |model.Paths| object.
dist: The |model.Distribution| for which customization is taking place.
config: The |config.CodeSignConfig| object.
"""
assert dist.channel_customize
resources_dir = os.path.join(paths.work, config.resources_dir)
old_name = '{}.manifest'.format(config.base_config.base_bundle_id)
new_name = '{}.manifest'.format(config.base_bundle_id)
commands.move_file(
os.path.join(
resources_dir, old_name, 'Contents', 'Resources', old_name
),
os.path.join(
resources_dir, old_name, 'Contents', 'Resources', new_name
),
)
commands.move_file(
os.path.join(resources_dir, old_name),
os.path.join(resources_dir, new_name),
)
manifest_plist_path = os.path.join(
resources_dir, new_name, 'Contents', 'Resources', new_name
)
with commands.PlistContext(
manifest_plist_path, rewrite=True
) as manifest_plist:
manifest_plist['pfm_domain'] = config.base_bundle_id
def _process_entitlements(paths, dist, config):
"""Copies the entitlements file out of the Packaging directory to the work
directory, possibly modifying it for channel customization.
Args:
paths: A |model.Paths| object.
dist: The |model.Distribution| for which customization is taking place.
config: The |config.CodeSignConfig| object.
"""
packaging_dir = paths.packaging_dir(config)
entitlements_names = set(
[
part.entitlements
for part in parts.get_parts(config).values()
if part.entitlements
]
)
for entitlements_name in sorted(entitlements_names):
entitlements_file = os.path.join(paths.work, entitlements_name)
commands.copy_files(
os.path.join(packaging_dir, entitlements_name), entitlements_file
)
if dist.channel_customize or config.inject_get_task_allow_entitlement:
with commands.PlistContext(
entitlements_file, rewrite=True
) as entitlements:
if dist.channel_customize and _ENT_APP_ID in entitlements:
app_id = entitlements[_ENT_APP_ID]
entitlements[_ENT_APP_ID] = app_id.replace(
config.base_config.base_bundle_id, config.base_bundle_id
)
if config.inject_get_task_allow_entitlement:
entitlements[_ENT_GET_TASK_ALLOW] = True
def customize_distribution(paths, dist, config):
"""Modifies the Chrome application bundle to specialize it with release
channel information.
Args:
paths: A |model.Paths| object.
dist: The |model.Distribution| for which customization is taking place.
config: The |config.CodeSignConfig| object.
"""
base_config = config.base_config
# First, copy duplicate the bundle, optionally to a new name.
if dist.channel_customize:
commands.move_file(
os.path.join(paths.work, base_config.app_dir),
os.path.join(paths.work, config.app_dir),
)
macos_dir = os.path.join(
paths.work, config.app_dir, 'Contents', 'MacOS'
)
commands.move_file(
os.path.join(macos_dir, base_config.app_product),
os.path.join(macos_dir, config.app_product),
)
_modify_plists(paths, dist, config)
_process_entitlements(paths, dist, config)
if dist.creator_code:
pkg_info_file = os.path.join(
paths.work, config.app_dir, 'Contents', 'PkgInfo'
)
commands.write_file(pkg_info_file, 'APPL{}'.format(dist.creator_code))
if dist.channel_customize:
_replace_icons(paths, dist, config)
_rename_enterprise_manifest(paths, dist, config)