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..cca2ba49d8ecefb06de48b4f257462ef0b4b6aa1 |
--- /dev/null |
+++ b/build/config/ios/ios_gen_plist.py |
@@ -0,0 +1,207 @@ |
+# 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 InterpolateList(values, substitutions): |
+ """Interpolates variable references into |value| using |substitutions|. |
+ |
+ Inputs: |
+ values: a list of values |
+ substitutions: a mapping of variable names to values |
+ |
+ Returns: |
+ A new list of values with all variables references ${VARIABLE} replaced |
+ by their value in |substitutions| or None if any of the variable has no |
+ subsitution. |
+ """ |
+ result = [] |
+ for value in values: |
+ interpolated = InterpolateValue(value, substitutions) |
+ if interpolated is None: |
+ return None |
+ result.append(interpolated) |
+ return result |
+ |
+ |
+def InterpolateString(value, substitutions): |
+ """Interpolates variable references into |value| using |substitutions|. |
+ |
+ Inputs: |
+ value: a string |
+ substitutions: a mapping of variable names to values |
+ |
+ Returns: |
+ A new string with all variables references ${VARIABLES} replaced by their |
+ value in |substitutions| or None if any of the variable has no substitution. |
+ """ |
+ result = value |
+ for match in reversed(list(SUBST_RE.finditer(value))): |
+ variable = match.group('id') |
+ if variable not in substitutions: |
+ return None |
+ # Some values need to be identifier and thus the variables references may |
+ # contains :modifier attributes to indicate how they should be converted |
+ # to identifiers ("identifier" replaces all invalid characters by '-' and |
+ # "rfc1034identifier" replaces them by "_" to make valid URI too). |
+ modifier = match.group('modifier') |
+ if modifier == 'identifier': |
+ interpolated = IDENT_RE.sub('-', substitutions[variable]) |
+ elif modifier == 'rfc1034identifier': |
+ interpolated = IDENT_RE.sub('_', substitutions[variable]) |
+ else: |
+ interpolated = substitutions[variable] |
+ result = result[:match.start()] + interpolated + result[match.end():] |
+ return result |
+ |
+ |
+def InterpolateValue(value, substitutions): |
+ """Interpolates variable references into |value| using |substitutions|. |
+ |
+ Inputs: |
+ value: a value, can be a dictionary, list, string or other |
+ substitutions: a mapping of variable names to values |
+ |
+ Returns: |
+ A new value with all variables references ${VARIABLES} replaced by their |
+ value in |substitutions| or None if any of the variable has no substitution. |
+ """ |
+ if isinstance(value, dict): |
+ return Interpolate(value, substitutions) |
+ if isinstance(value, list): |
+ return InterpolateList(value, substitutions) |
+ if isinstance(value, str): |
+ return InterpolateString(value, substitutions) |
+ return value |
+ |
+ |
+def Interpolate(plist, substitutions): |
+ """Interpolates variable references into |value| using |substitutions|. |
+ |
+ Inputs: |
+ plist: a dictionary representing a Property List (.plist) file |
+ substitutions: a mapping of variable names to values |
+ |
+ Returns: |
+ A new plist with all variables references ${VARIABLES} replaced by their |
+ value in |substitutions|. All values that contains references with no |
+ substitutions will be removed and the corresponding key will be cleared |
+ from the plist (not recursively). |
+ """ |
+ result = {} |
+ for key in plist: |
+ value = InterpolateValue(plist[key], substitutions) |
+ if value is not None: |
+ result[key] = value |
+ return result |
+ |
+ |
+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 MergePList(plist1, plist2): |
+ """Merges |plist1| with |plist2| recursively. |
+ |
+ Creates a new dictionary representing a Property List (.plist) files by |
+ merging the two dictionary |plist1| and |plist2| recursively (only for |
+ dictionary values). |
+ |
+ Args: |
+ plist1: a dictionary representing a Property List (.plist) file |
+ plist2: a dictionary representing a Property List (.plist) file |
+ |
+ Returns: |
+ A new dictionary representing a Property List (.plist) file by merging |
+ |plist1| with |plist2|. If any value is a dictionary, they are merged |
+ recursively, otherwise |plist2| value is used. |
+ """ |
+ if not isinstance(plist1, dict) or not isinstance(plist2, dict): |
+ if plist2 is not None: |
+ return plist2 |
+ else: |
+ return plist1 |
+ result = {} |
+ for key in set(plist1) | set(plist2): |
+ if key in plist2: |
+ value = plist2[key] |
+ else: |
+ value = plist1[key] |
+ if isinstance(value, dict): |
+ value = MergePList(plist1.get(key, None), plist2.get(key, None)) |
+ result[key] = value |
+ return result |
+ |
+ |
+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 filename in args.path: |
+ data = MergePList(data, LoadPList(filename)) |
+ data = Interpolate(data, substitutions) |
+ SavePList(args.output, data) |
+ return 0 |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |