diff options
Diffstat (limited to 'third_party/libwebrtc/build/apple/tweak_info_plist.py')
-rwxr-xr-x | third_party/libwebrtc/build/apple/tweak_info_plist.py | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/apple/tweak_info_plist.py b/third_party/libwebrtc/build/apple/tweak_info_plist.py new file mode 100755 index 0000000000..76f64dc37c --- /dev/null +++ b/third_party/libwebrtc/build/apple/tweak_info_plist.py @@ -0,0 +1,447 @@ +#!/usr/bin/env python + +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# +# Xcode supports build variable substitutions and CPP; sadly, that doesn't work +# because: +# +# 1. Xcode wants to do the Info.plist work before it runs any build phases, +# this means if we were to generate a .h file for INFOPLIST_PREFIX_HEADER +# we'd have to put it in another target so it runs in time. +# 2. Xcode also doesn't check to see if the header being used as a prefix for +# the Info.plist has changed. So even if we updated it, it's only looking +# at the modtime of the info.plist to see if that's changed. +# +# So, we work around all of this by making a script build phase that will run +# during the app build, and simply update the info.plist in place. This way +# by the time the app target is done, the info.plist is correct. +# + +from __future__ import print_function + +import optparse +import os +import plistlib +import re +import subprocess +import sys +import tempfile + +TOP = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + + +def _ConvertPlist(source_plist, output_plist, fmt): + """Convert |source_plist| to |fmt| and save as |output_plist|.""" + assert sys.version_info.major == 2, "Use plistlib directly in Python 3" + return subprocess.call( + ['plutil', '-convert', fmt, '-o', output_plist, source_plist]) + + +def _GetOutput(args): + """Runs a subprocess and waits for termination. Returns (stdout, returncode) + of the process. stderr is attached to the parent.""" + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + stdout, _ = proc.communicate() + return stdout.decode('UTF-8'), proc.returncode + + +def _RemoveKeys(plist, *keys): + """Removes a varargs of keys from the plist.""" + for key in keys: + try: + del plist[key] + except KeyError: + pass + + +def _ApplyVersionOverrides(version, keys, overrides, separator='.'): + """Applies version overrides. + + Given a |version| string as "a.b.c.d" (assuming a default separator) with + version components named by |keys| then overrides any value that is present + in |overrides|. + + >>> _ApplyVersionOverrides('a.b', ['major', 'minor'], {'minor': 'd'}) + 'a.d' + """ + if not overrides: + return version + version_values = version.split(separator) + for i, (key, value) in enumerate(zip(keys, version_values)): + if key in overrides: + version_values[i] = overrides[key] + return separator.join(version_values) + + +def _GetVersion(version_format, values, overrides=None): + """Generates a version number according to |version_format| using the values + from |values| or |overrides| if given.""" + result = version_format + for key in values: + if overrides and key in overrides: + value = overrides[key] + else: + value = values[key] + result = result.replace('@%s@' % key, value) + return result + + +def _AddVersionKeys(plist, version_format_for_key, version=None, + overrides=None): + """Adds the product version number into the plist. Returns True on success and + False on error. The error will be printed to stderr.""" + if not version: + # Pull in the Chrome version number. + VERSION_TOOL = os.path.join(TOP, 'build/util/version.py') + VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') + (stdout, retval) = _GetOutput([ + VERSION_TOOL, '-f', VERSION_FILE, '-t', + '@MAJOR@.@MINOR@.@BUILD@.@PATCH@' + ]) + + # If the command finished with a non-zero return code, then report the + # error up. + if retval != 0: + return False + + version = stdout.strip() + + # Parse the given version number, that should be in MAJOR.MINOR.BUILD.PATCH + # format (where each value is a number). Note that str.isdigit() returns + # True if the string is composed only of digits (and thus match \d+ regexp). + groups = version.split('.') + if len(groups) != 4 or not all(element.isdigit() for element in groups): + print('Invalid version string specified: "%s"' % version, file=sys.stderr) + return False + values = dict(zip(('MAJOR', 'MINOR', 'BUILD', 'PATCH'), groups)) + + for key in version_format_for_key: + plist[key] = _GetVersion(version_format_for_key[key], values, overrides) + + # Return with no error. + return True + + +def _DoSCMKeys(plist, add_keys): + """Adds the SCM information, visible in about:version, to property list. If + |add_keys| is True, it will insert the keys, otherwise it will remove them.""" + scm_revision = None + if add_keys: + # Pull in the Chrome revision number. + VERSION_TOOL = os.path.join(TOP, 'build/util/version.py') + LASTCHANGE_FILE = os.path.join(TOP, 'build/util/LASTCHANGE') + (stdout, retval) = _GetOutput( + [VERSION_TOOL, '-f', LASTCHANGE_FILE, '-t', '@LASTCHANGE@']) + if retval: + return False + scm_revision = stdout.rstrip() + + # See if the operation failed. + _RemoveKeys(plist, 'SCMRevision') + if scm_revision != None: + plist['SCMRevision'] = scm_revision + elif add_keys: + print('Could not determine SCM revision. This may be OK.', file=sys.stderr) + + return True + + +def _AddBreakpadKeys(plist, branding, platform, staging): + """Adds the Breakpad keys. This must be called AFTER _AddVersionKeys() and + also requires the |branding| argument.""" + plist['BreakpadReportInterval'] = '3600' # Deliberately a string. + plist['BreakpadProduct'] = '%s_%s' % (branding, platform) + plist['BreakpadProductDisplay'] = branding + if staging: + plist['BreakpadURL'] = 'https://clients2.google.com/cr/staging_report' + else: + plist['BreakpadURL'] = 'https://clients2.google.com/cr/report' + + # These are both deliberately strings and not boolean. + plist['BreakpadSendAndExit'] = 'YES' + plist['BreakpadSkipConfirm'] = 'YES' + + +def _RemoveBreakpadKeys(plist): + """Removes any set Breakpad keys.""" + _RemoveKeys(plist, 'BreakpadURL', 'BreakpadReportInterval', 'BreakpadProduct', + 'BreakpadProductDisplay', 'BreakpadVersion', + 'BreakpadSendAndExit', 'BreakpadSkipConfirm') + + +def _TagSuffixes(): + # Keep this list sorted in the order that tag suffix components are to + # appear in a tag value. That is to say, it should be sorted per ASCII. + components = ('full', ) + assert tuple(sorted(components)) == components + + components_len = len(components) + combinations = 1 << components_len + tag_suffixes = [] + for combination in range(0, combinations): + tag_suffix = '' + for component_index in range(0, components_len): + if combination & (1 << component_index): + tag_suffix += '-' + components[component_index] + tag_suffixes.append(tag_suffix) + return tag_suffixes + + +def _AddKeystoneKeys(plist, bundle_identifier, base_tag): + """Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and + also requires the |bundle_identifier| argument (com.example.product).""" + plist['KSVersion'] = plist['CFBundleShortVersionString'] + plist['KSProductID'] = bundle_identifier + plist['KSUpdateURL'] = 'https://tools.google.com/service/update2' + + _RemoveKeys(plist, 'KSChannelID') + if base_tag != '': + plist['KSChannelID'] = base_tag + for tag_suffix in _TagSuffixes(): + if tag_suffix: + plist['KSChannelID' + tag_suffix] = base_tag + tag_suffix + + +def _RemoveKeystoneKeys(plist): + """Removes any set Keystone keys.""" + _RemoveKeys(plist, 'KSVersion', 'KSProductID', 'KSUpdateURL') + + tag_keys = ['KSChannelID'] + for tag_suffix in _TagSuffixes(): + tag_keys.append('KSChannelID' + tag_suffix) + _RemoveKeys(plist, *tag_keys) + + +def _AddGTMKeys(plist, platform): + """Adds the GTM metadata keys. This must be called AFTER _AddVersionKeys().""" + plist['GTMUserAgentID'] = plist['CFBundleName'] + if platform == 'ios': + plist['GTMUserAgentVersion'] = plist['CFBundleVersion'] + else: + plist['GTMUserAgentVersion'] = plist['CFBundleShortVersionString'] + + +def _RemoveGTMKeys(plist): + """Removes any set GTM metadata keys.""" + _RemoveKeys(plist, 'GTMUserAgentID', 'GTMUserAgentVersion') + + +def Main(argv): + parser = optparse.OptionParser('%prog [options]') + parser.add_option('--plist', + dest='plist_path', + action='store', + type='string', + default=None, + help='The path of the plist to tweak.') + parser.add_option('--output', dest='plist_output', action='store', + type='string', default=None, help='If specified, the path to output ' + \ + 'the tweaked plist, rather than overwriting the input.') + parser.add_option('--breakpad', + dest='use_breakpad', + action='store', + type='int', + default=False, + help='Enable Breakpad [1 or 0]') + parser.add_option( + '--breakpad_staging', + dest='use_breakpad_staging', + action='store_true', + default=False, + help='Use staging breakpad to upload reports. Ignored if --breakpad=0.') + parser.add_option('--keystone', + dest='use_keystone', + action='store', + type='int', + default=False, + help='Enable Keystone [1 or 0]') + parser.add_option('--keystone-base-tag', + default='', + help='Base Keystone tag to set') + parser.add_option('--scm', + dest='add_scm_info', + action='store', + type='int', + default=True, + help='Add SCM metadata [1 or 0]') + parser.add_option('--branding', + dest='branding', + action='store', + type='string', + default=None, + help='The branding of the binary') + parser.add_option('--bundle_id', + dest='bundle_identifier', + action='store', + type='string', + default=None, + help='The bundle id of the binary') + parser.add_option('--platform', + choices=('ios', 'mac'), + default='mac', + help='The target platform of the bundle') + parser.add_option('--add-gtm-metadata', + dest='add_gtm_info', + action='store', + type='int', + default=False, + help='Add GTM metadata [1 or 0]') + # TODO(crbug.com/1140474): Remove once iOS 14.2 reaches mass adoption. + parser.add_option('--lock-to-version', + help='Set CFBundleVersion to given value + @MAJOR@@PATH@') + parser.add_option( + '--version-overrides', + action='append', + help='Key-value pair to override specific component of version ' + 'like key=value (can be passed multiple time to configure ' + 'more than one override)') + parser.add_option('--format', + choices=('binary1', 'xml1'), + default='xml1', + help='Format to use when writing property list ' + '(default: %(default)s)') + parser.add_option('--version', + dest='version', + action='store', + type='string', + default=None, + help='The version string [major.minor.build.patch]') + (options, args) = parser.parse_args(argv) + + if len(args) > 0: + print(parser.get_usage(), file=sys.stderr) + return 1 + + if not options.plist_path: + print('No --plist specified.', file=sys.stderr) + return 1 + + # Read the plist into its parsed format. Convert the file to 'xml1' as + # plistlib only supports that format in Python 2.7. + with tempfile.NamedTemporaryFile() as temp_info_plist: + if sys.version_info.major == 2: + retcode = _ConvertPlist(options.plist_path, temp_info_plist.name, 'xml1') + if retcode != 0: + return retcode + plist = plistlib.readPlist(temp_info_plist.name) + else: + with open(options.plist_path, 'rb') as f: + plist = plistlib.load(f) + + # Convert overrides. + overrides = {} + if options.version_overrides: + for pair in options.version_overrides: + if not '=' in pair: + print('Invalid value for --version-overrides:', pair, file=sys.stderr) + return 1 + key, value = pair.split('=', 1) + overrides[key] = value + if key not in ('MAJOR', 'MINOR', 'BUILD', 'PATCH'): + print('Unsupported key for --version-overrides:', key, file=sys.stderr) + return 1 + + if options.platform == 'mac': + version_format_for_key = { + # Add public version info so "Get Info" works. + 'CFBundleShortVersionString': '@MAJOR@.@MINOR@.@BUILD@.@PATCH@', + + # Honor the 429496.72.95 limit. The maximum comes from splitting + # 2^32 - 1 into 6, 2, 2 digits. The limitation was present in Tiger, + # but it could have been fixed in later OS release, but hasn't been + # tested (it's easy enough to find out with "lsregister -dump). + # http://lists.apple.com/archives/carbon-dev/2006/Jun/msg00139.html + # BUILD will always be an increasing value, so BUILD_PATH gives us + # something unique that meetings what LS wants. + 'CFBundleVersion': '@BUILD@.@PATCH@', + } + else: + # TODO(crbug.com/1140474): Remove once iOS 14.2 reaches mass adoption. + if options.lock_to_version: + # Pull in the PATCH number and format it to 3 digits. + VERSION_TOOL = os.path.join(TOP, 'build/util/version.py') + VERSION_FILE = os.path.join(TOP, 'chrome/VERSION') + (stdout, + retval) = _GetOutput([VERSION_TOOL, '-f', VERSION_FILE, '-t', '@PATCH@']) + if retval != 0: + return 2 + patch = '{:03d}'.format(int(stdout)) + version_format_for_key = { + 'CFBundleShortVersionString': '@MAJOR@.@BUILD@.@PATCH@', + 'CFBundleVersion': options.lock_to_version + '.@MAJOR@' + patch + } + else: + version_format_for_key = { + 'CFBundleShortVersionString': '@MAJOR@.@BUILD@.@PATCH@', + 'CFBundleVersion': '@MAJOR@.@MINOR@.@BUILD@.@PATCH@' + } + + if options.use_breakpad: + version_format_for_key['BreakpadVersion'] = \ + '@MAJOR@.@MINOR@.@BUILD@.@PATCH@' + + # Insert the product version. + if not _AddVersionKeys(plist, + version_format_for_key, + version=options.version, + overrides=overrides): + return 2 + + # Add Breakpad if configured to do so. + if options.use_breakpad: + if options.branding is None: + print('Use of Breakpad requires branding.', file=sys.stderr) + return 1 + # Map "target_os" passed from gn via the --platform parameter + # to the platform as known by breakpad. + platform = {'mac': 'Mac', 'ios': 'iOS'}[options.platform] + _AddBreakpadKeys(plist, options.branding, platform, + options.use_breakpad_staging) + else: + _RemoveBreakpadKeys(plist) + + # Add Keystone if configured to do so. + if options.use_keystone: + if options.bundle_identifier is None: + print('Use of Keystone requires the bundle id.', file=sys.stderr) + return 1 + _AddKeystoneKeys(plist, options.bundle_identifier, + options.keystone_base_tag) + else: + _RemoveKeystoneKeys(plist) + + # Adds or removes any SCM keys. + if not _DoSCMKeys(plist, options.add_scm_info): + return 3 + + # Add GTM metadata keys. + if options.add_gtm_info: + _AddGTMKeys(plist, options.platform) + else: + _RemoveGTMKeys(plist) + + output_path = options.plist_path + if options.plist_output is not None: + output_path = options.plist_output + + # Now that all keys have been mutated, rewrite the file. + # Convert Info.plist to the format requested by the --format flag. Any + # format would work on Mac but iOS requires specific format. + if sys.version_info.major == 2: + with tempfile.NamedTemporaryFile() as temp_info_plist: + plistlib.writePlist(plist, temp_info_plist.name) + return _ConvertPlist(temp_info_plist.name, output_path, options.format) + with open(output_path, 'wb') as f: + plist_format = {'binary1': plistlib.FMT_BINARY, 'xml1': plistlib.FMT_XML} + plistlib.dump(plist, f, fmt=plist_format[options.format]) + + +if __name__ == '__main__': + # TODO(https://crbug.com/941669): Temporary workaround until all scripts use + # python3 by default. + if sys.version_info[0] < 3: + os.execvp('python3', ['python3'] + sys.argv) + sys.exit(Main(sys.argv[1:])) |