summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/apple/plist_util.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/build/apple/plist_util.py')
-rw-r--r--third_party/libwebrtc/build/apple/plist_util.py265
1 files changed, 265 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/apple/plist_util.py b/third_party/libwebrtc/build/apple/plist_util.py
new file mode 100644
index 0000000000..54cf46176b
--- /dev/null
+++ b/third_party/libwebrtc/build/apple/plist_util.py
@@ -0,0 +1,265 @@
+# 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 codecs
+import plistlib
+import os
+import re
+import subprocess
+import sys
+import tempfile
+import shlex
+
+if sys.version_info.major < 3:
+ basestring_compat = basestring
+else:
+ basestring_compat = str
+
+# Xcode substitutes variables like ${PRODUCT_NAME} or $(PRODUCT_NAME) when
+# compiling Info.plist. It also supports supports modifiers like :identifier
+# or :rfc1034identifier. SUBSTITUTION_REGEXP_LIST is a list of regular
+# expressions matching a variable substitution pattern with an optional
+# modifier, while INVALID_CHARACTER_REGEXP matches all characters that are
+# not valid in an "identifier" value (used when applying the modifier).
+INVALID_CHARACTER_REGEXP = re.compile(r'[_/\s]')
+SUBSTITUTION_REGEXP_LIST = (
+ re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}'),
+ re.compile(r'\$\((?P<id>[^}]*?)(?P<modifier>:[^}]*)?\)'),
+)
+
+
+class SubstitutionError(Exception):
+ def __init__(self, key):
+ super(SubstitutionError, self).__init__()
+ self.key = key
+
+ def __str__(self):
+ return "SubstitutionError: {}".format(self.key)
+
+
+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|. Raises SubstitutionError if a variable has no
+ substitution.
+ """
+
+ def repl(match):
+ variable = match.group('id')
+ if variable not in substitutions:
+ raise SubstitutionError(variable)
+ # 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':
+ return INVALID_CHARACTER_REGEXP.sub('_', substitutions[variable])
+ elif modifier == ':rfc1034identifier':
+ return INVALID_CHARACTER_REGEXP.sub('-', substitutions[variable])
+ else:
+ return substitutions[variable]
+
+ for substitution_regexp in SUBSTITUTION_REGEXP_LIST:
+ value = substitution_regexp.sub(repl, value)
+ return value
+
+
+def Interpolate(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|. Raises SubstitutionError if a variable has no
+ substitution.
+ """
+ if isinstance(value, dict):
+ return {k: Interpolate(v, substitutions) for k, v in value.items()}
+ if isinstance(value, list):
+ return [Interpolate(v, substitutions) for v in value]
+ if isinstance(value, basestring_compat):
+ return InterpolateString(value, substitutions)
+ return value
+
+
+def LoadPList(path):
+ """Loads Plist at |path| and returns it as a dictionary."""
+ if sys.version_info.major == 2:
+ fd, name = tempfile.mkstemp()
+ try:
+ subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path])
+ with os.fdopen(fd, 'rb') as f:
+ return plistlib.readPlist(f)
+ finally:
+ os.unlink(name)
+ else:
+ with open(path, 'rb') as f:
+ return plistlib.load(f)
+
+
+def SavePList(path, format, data):
+ """Saves |data| as a Plist to |path| in the specified |format|."""
+ # The below does not replace the destination file but update it in place,
+ # so if more than one hardlink points to destination all of them will be
+ # modified. This is not what is expected, so delete destination file if
+ # it does exist.
+ if os.path.exists(path):
+ os.unlink(path)
+ if sys.version_info.major == 2:
+ fd, name = tempfile.mkstemp()
+ try:
+ with os.fdopen(fd, 'wb') as f:
+ plistlib.writePlist(data, f)
+ subprocess.check_call(['plutil', '-convert', format, '-o', path, name])
+ finally:
+ os.unlink(name)
+ else:
+ with open(path, 'wb') as f:
+ plist_format = {'binary1': plistlib.FMT_BINARY, 'xml1': plistlib.FMT_XML}
+ plistlib.dump(data, f, fmt=plist_format[format])
+
+
+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). List value will be concatenated.
+
+ 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 values are list, they
+ are concatenated.
+ """
+ result = plist1.copy()
+ for key, value in plist2.items():
+ if isinstance(value, dict):
+ old_value = result.get(key)
+ if isinstance(old_value, dict):
+ value = MergePList(old_value, value)
+ if isinstance(value, list):
+ value = plist1.get(key, []) + plist2.get(key, [])
+ result[key] = value
+ return result
+
+
+class Action(object):
+ """Class implementing one action supported by the script."""
+
+ @classmethod
+ def Register(cls, subparsers):
+ parser = subparsers.add_parser(cls.name, help=cls.help)
+ parser.set_defaults(func=cls._Execute)
+ cls._Register(parser)
+
+
+class MergeAction(Action):
+ """Class to merge multiple plist files."""
+
+ name = 'merge'
+ help = 'merge multiple plist files'
+
+ @staticmethod
+ def _Register(parser):
+ parser.add_argument('-o',
+ '--output',
+ required=True,
+ help='path to the output plist file')
+ parser.add_argument('-f',
+ '--format',
+ required=True,
+ choices=('xml1', 'binary1'),
+ help='format of the plist file to generate')
+ parser.add_argument(
+ '-x',
+ '--xcode-version',
+ help='version of Xcode, ignored (can be used to force rebuild)')
+ parser.add_argument('path', nargs="+", help='path to plist files to merge')
+
+ @staticmethod
+ def _Execute(args):
+ data = {}
+ for filename in args.path:
+ data = MergePList(data, LoadPList(filename))
+ SavePList(args.output, args.format, data)
+
+
+class SubstituteAction(Action):
+ """Class implementing the variable substitution in a plist file."""
+
+ name = 'substitute'
+ help = 'perform pattern substitution in a plist file'
+
+ @staticmethod
+ def _Register(parser):
+ parser.add_argument('-o',
+ '--output',
+ required=True,
+ help='path to the output plist file')
+ parser.add_argument('-t',
+ '--template',
+ required=True,
+ help='path to the template file')
+ parser.add_argument('-s',
+ '--substitution',
+ action='append',
+ default=[],
+ help='substitution rule in the format key=value')
+ parser.add_argument('-f',
+ '--format',
+ required=True,
+ choices=('xml1', 'binary1'),
+ help='format of the plist file to generate')
+ parser.add_argument(
+ '-x',
+ '--xcode-version',
+ help='version of Xcode, ignored (can be used to force rebuild)')
+
+ @staticmethod
+ def _Execute(args):
+ substitutions = {}
+ for substitution in args.substitution:
+ key, value = substitution.split('=', 1)
+ substitutions[key] = value
+ data = Interpolate(LoadPList(args.template), substitutions)
+ SavePList(args.output, args.format, data)
+
+
+def Main():
+ # Cache this codec so that plistlib can find it. See
+ # https://crbug.com/1005190#c2 for more details.
+ codecs.lookup('utf-8')
+
+ parser = argparse.ArgumentParser(description='manipulate plist files')
+ subparsers = parser.add_subparsers()
+
+ for action in [MergeAction, SubstituteAction]:
+ action.Register(subparsers)
+
+ args = parser.parse_args()
+ args.func(args)
+
+
+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())