Chromium Code Reviews| 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..b1f502e8c22dc04ffa7b22f4ca12ac9503cf9baa |
| --- /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}}. |
|
Dirk Pranke
2016/03/01 23:56:29
Interesting, I didn't know about fromfile_prefix_c
sdefresne
2016/03/11 17:57:11
Ack.
|
| + """ |
| + |
| + def convert_arg_line_to_args(self, arg_line): |
| + return shlex.split(arg_line) |
| + |
| + |
| +def InterpolateList(value, substitutions): |
| + """Interpolates variable references into |value| using |substitutions|. |
| + |
| + Inputs: |
| + value: a list of values |
|
Dirk Pranke
2016/03/01 23:56:29
nit: if this is actually a list, I'd call it 'valu
sdefresne
2016/03/11 17:57:11
Done.
|
| + substitutions: a mapping of variable names to values |
| + |
| + Returns: |
| + A new list of values with all variable references ${VARIABLE} replaced |
| + by their value in |substitutions| or None if any of the variable has no |
|
Dirk Pranke
2016/03/01 23:56:29
nit: s/variable/variables/
sdefresne
2016/03/11 17:57:11
Done.
|
| + subsitution. |
| + """ |
| + result = [] |
| + for v in value: |
| + interpolated = InterpolateValue(v, 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 variable 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 value need to be identifier, and thus the variable references may |
|
Dirk Pranke
2016/03/01 23:56:29
Nit: s/value/values/ , s/identifier/identifiers/
sdefresne
2016/03/11 17:57:11
Done.
|
| + # contains :modifier attributes to indicate how they should be converted |
| + # to identifiers ("identifier" replace all invalid characters by '-' and |
|
Dirk Pranke
2016/03/01 23:56:29
s/replace/replaces
sdefresne
2016/03/11 17:57:11
Done.
|
| + # "rfc1034identifier" replace them by "_" to make valid URI too). |
|
Dirk Pranke
2016/03/01 23:56:30
s/replace/replaces
sdefresne
2016/03/11 17:57:11
Done.
|
| + 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 variable 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 variable 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()) |