| # 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) |