| Index: build/config/ios/ios_gen_plist.py
|
| diff --git a/build/config/ios/ios_gen_plist.py b/build/config/ios/ios_gen_plist.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..937c200a4a259cf1676bd32384cbb7e43686c02e
|
| --- /dev/null
|
| +++ b/build/config/ios/ios_gen_plist.py
|
| @@ -0,0 +1,143 @@
|
| +# Copyright 2016 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.
|
| +
|
| +import argparse
|
| +import plistlib
|
| +import os
|
| +import re
|
| +import subprocess
|
| +import sys
|
| +import tempfile
|
| +import shlex
|
| +
|
| +
|
| +# Xcode substitutes variables like ${PRODUCT_NAME} when compiling Info.plist.
|
| +# It also supports supports modifiers like :identifier or :rfc1034identifier.
|
| +# SUBST_RE matches a variable substitution pattern with an optional modifier,
|
| +# while IDENT_RE matches all characters that are not valid in an "identifier"
|
| +# value (used when applying the modifier).
|
| +SUBST_RE = re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}')
|
| +IDENT_RE = re.compile(r'[/\s]')
|
| +
|
| +
|
| +class ArgumentParser(argparse.ArgumentParser):
|
| + """Subclass of argparse.ArgumentParser to work with GN response files.
|
| +
|
| + GN response file writes all the arguments on a single line and assumes
|
| + that the python script uses shlext.split() to extract them. Since the
|
| + default ArgumentParser expects a single argument per line, we need to
|
| + provide a subclass to have the correct support for @{{response_file_name}}.
|
| + """
|
| +
|
| + def convert_arg_line_to_args(self, arg_line):
|
| + return shlex.split(arg_line)
|
| +
|
| +
|
| +def Merge(plist1, plist2):
|
| + """Recursively merges |plist2| into |plist1|."""
|
| + if not plist1:
|
| + return plist2
|
| + for key in plist2:
|
| + value = plist2[key]
|
| + if isinstance(value, dict):
|
| + plist1[key] = Merge(plist1.get(key, {}), value)
|
| + else:
|
| + plist1[key] = value
|
| + return plist1
|
| +
|
| +
|
| +def Interpolate(plist, substitutions):
|
| + """Interpolates ${variable} into |plist| using mapping in |substitutions|."""
|
| + result = []
|
| + groups = SUBST_RE.split(plistlib.writePlistToString(plist))
|
| + # SUBST_RE defines two patterns, so given a string, SUBST_RE.split will
|
| + # return a list whose position correspond to an unmatched fragment, a
|
| + # match identifier or a match modifier (may be None as it is optional).
|
| + # Enumerate over the value and check the index modulo 3 to figure out
|
| + # the category of the value.
|
| + for i, value in enumerate(groups):
|
| + if i % 3 == 0:
|
| + result.append(value)
|
| + elif i % 3 == 1:
|
| + if value in substitutions:
|
| + result.append(substitutions[value])
|
| + else:
|
| + result.append('${%s}' % value)
|
| + elif i % 3 == 2:
|
| + value, modifier = result[-1], value
|
| + # Apply the modifier if defined (otherwise the value will be None). At
|
| + # the beginning Xcode only supported :identifier that replaced all the
|
| + # characters that are invalid by an hyphen, however, this is an invalid
|
| + # character in a bundle identifier which is a reversed domain name and
|
| + # so :rfc1034identifier was created that instead uses an underscore.
|
| + if modifier == 'identifier':
|
| + value = IDENT_RE.sub('-', value)
|
| + elif modifier == 'rfc1034identifier':
|
| + value = IDENT_RE.sub('_', value)
|
| + result[-1] = value
|
| + return CleanPlist(plistlib.readPlistFromString(''.join(result)))
|
| +
|
| +
|
| +def CleanPlist(plist):
|
| + """Removes all key of |plist| with unexpanded variables substitution."""
|
| + key_to_remove = []
|
| + for key in plist:
|
| + value = plist[key]
|
| + data = plistlib.writePlistToString(value)
|
| + if SUBST_RE.search(data):
|
| + key_to_remove.append(key)
|
| + for key in key_to_remove:
|
| + del plist[key]
|
| + return plist
|
| +
|
| +
|
| +def LoadPList(path):
|
| + """Loads Plist at |path| and returns it as a dictionary."""
|
| + fd, name = tempfile.mkstemp()
|
| + try:
|
| + subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path])
|
| + with os.fdopen(fd, 'r') as f:
|
| + return plistlib.readPlist(f)
|
| + finally:
|
| + os.unlink(name)
|
| +
|
| +
|
| +def SavePList(path, data):
|
| + """Saves |data| as a Plist to |path| in binary1 format."""
|
| + fd, name = tempfile.mkstemp()
|
| + try:
|
| + with os.fdopen(fd, 'w') as f:
|
| + plistlib.writePlist(data, f)
|
| + subprocess.check_call(['plutil', '-convert', 'binary1', '-o', path, name])
|
| + finally:
|
| + os.unlink(name)
|
| +
|
| +
|
| +def main():
|
| + parser = ArgumentParser(
|
| + description='A script to generate iOS application Info.plist.',
|
| + fromfile_prefix_chars='@')
|
| + parser.add_argument('-o', '--output', required=True,
|
| + help='Path to output plist file.')
|
| + parser.add_argument('-s', '--subst', action='append', default=[],
|
| + help='Substitution rule in the format "key=value".')
|
| + parser.add_argument('path', nargs="+", help='Path to input plist files.')
|
| + args = parser.parse_args()
|
| +
|
| + substitutions = {}
|
| + for subst in args.subst:
|
| + key, value = subst.split('=', 1)
|
| + substitutions[key] = value
|
| +
|
| + data = {}
|
| + for path in args.path:
|
| + data = Merge(data, LoadPList(path))
|
| +
|
| + data = Interpolate(data, substitutions)
|
| + SavePList(args.output, data)
|
| + return 0
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|