Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(573)

Side by Side Diff: build/config/mac/plist_util.py

Issue 2367923002: Fail to build with unbound Info.plist substitutions instead of silently dropping them. (Closed)
Patch Set: Rebase to pick up crrev/c/555912 Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « build/config/mac/BuildInfo.plist ('k') | ui/base/BUILD.gn » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The Chromium Authors. All rights reserved. 1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import argparse 5 import argparse
6 import plistlib 6 import plistlib
7 import os 7 import os
8 import re 8 import re
9 import subprocess 9 import subprocess
10 import sys 10 import sys
11 import tempfile 11 import tempfile
12 import shlex 12 import shlex
13 13
14 14
15 # Xcode substitutes variables like ${PRODUCT_NAME} when compiling Info.plist. 15 # Xcode substitutes variables like ${PRODUCT_NAME} when compiling Info.plist.
16 # It also supports supports modifiers like :identifier or :rfc1034identifier. 16 # It also supports supports modifiers like :identifier or :rfc1034identifier.
17 # SUBST_RE matches a variable substitution pattern with an optional modifier, 17 # SUBST_RE matches a variable substitution pattern with an optional modifier,
18 # while IDENT_RE matches all characters that are not valid in an "identifier" 18 # while IDENT_RE matches all characters that are not valid in an "identifier"
19 # value (used when applying the modifier). 19 # value (used when applying the modifier).
20 SUBST_RE = re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}') 20 SUBST_RE = re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}')
21 IDENT_RE = re.compile(r'[_/\s]') 21 IDENT_RE = re.compile(r'[_/\s]')
22 22
23 23
24 def InterpolateList(values, substitutions): 24 class SubstitutionError(Exception):
25 """Interpolates variable references into |value| using |substitutions|. 25 def __init__(self, key):
26 super(SubstitutionError, self).__init__()
27 self.key = key
26 28
27 Inputs: 29 def __str__(self):
28 values: a list of values 30 return "SubstitutionError: {}".format(self.key)
29 substitutions: a mapping of variable names to values
30
31 Returns:
32 A new list of values with all variables references ${VARIABLE} replaced
33 by their value in |substitutions| or None if any of the variable has no
34 subsitution.
35 """
36 result = []
37 for value in values:
38 interpolated = InterpolateValue(value, substitutions)
39 if interpolated is None:
40 return None
41 result.append(interpolated)
42 return result
43 31
44 32
45 def InterpolateString(value, substitutions): 33 def InterpolateString(value, substitutions):
46 """Interpolates variable references into |value| using |substitutions|. 34 """Interpolates variable references into |value| using |substitutions|.
47 35
48 Inputs: 36 Inputs:
49 value: a string 37 value: a string
50 substitutions: a mapping of variable names to values 38 substitutions: a mapping of variable names to values
51 39
52 Returns: 40 Returns:
53 A new string with all variables references ${VARIABLES} replaced by their 41 A new string with all variables references ${VARIABLES} replaced by their
54 value in |substitutions| or None if any of the variable has no substitution. 42 value in |substitutions|. Raises SubstitutionError if a variable has no
43 substitution.
55 """ 44 """
56 result = value 45 def repl(match):
57 for match in reversed(list(SUBST_RE.finditer(value))):
58 variable = match.group('id') 46 variable = match.group('id')
59 if variable not in substitutions: 47 if variable not in substitutions:
60 return None 48 raise SubstitutionError(variable)
61 # Some values need to be identifier and thus the variables references may 49 # Some values need to be identifier and thus the variables references may
62 # contains :modifier attributes to indicate how they should be converted 50 # contains :modifier attributes to indicate how they should be converted
63 # to identifiers ("identifier" replaces all invalid characters by '_' and 51 # to identifiers ("identifier" replaces all invalid characters by '_' and
64 # "rfc1034identifier" replaces them by "-" to make valid URI too). 52 # "rfc1034identifier" replaces them by "-" to make valid URI too).
65 modifier = match.group('modifier') 53 modifier = match.group('modifier')
66 if modifier == ':identifier': 54 if modifier == ':identifier':
67 interpolated = IDENT_RE.sub('_', substitutions[variable]) 55 return IDENT_RE.sub('_', substitutions[variable])
68 elif modifier == ':rfc1034identifier': 56 elif modifier == ':rfc1034identifier':
69 interpolated = IDENT_RE.sub('-', substitutions[variable]) 57 return IDENT_RE.sub('-', substitutions[variable])
70 else: 58 else:
71 interpolated = substitutions[variable] 59 return substitutions[variable]
72 result = result[:match.start()] + interpolated + result[match.end():] 60 return SUBST_RE.sub(repl, value)
73 return result
74 61
75 62
76 def InterpolateValue(value, substitutions): 63 def Interpolate(value, substitutions):
77 """Interpolates variable references into |value| using |substitutions|. 64 """Interpolates variable references into |value| using |substitutions|.
78 65
79 Inputs: 66 Inputs:
80 value: a value, can be a dictionary, list, string or other 67 value: a value, can be a dictionary, list, string or other
81 substitutions: a mapping of variable names to values 68 substitutions: a mapping of variable names to values
82 69
83 Returns: 70 Returns:
84 A new value with all variables references ${VARIABLES} replaced by their 71 A new value with all variables references ${VARIABLES} replaced by their
85 value in |substitutions| or None if any of the variable has no substitution. 72 value in |substitutions|. Raises SubstitutionError if a variable has no
73 substitution.
86 """ 74 """
87 if isinstance(value, dict): 75 if isinstance(value, dict):
88 return Interpolate(value, substitutions) 76 return {k: Interpolate(v, substitutions) for k, v in value.iteritems()}
89 if isinstance(value, list): 77 if isinstance(value, list):
90 return InterpolateList(value, substitutions) 78 return [Interpolate(v, substitutions) for v in value]
91 if isinstance(value, str): 79 if isinstance(value, str):
92 return InterpolateString(value, substitutions) 80 return InterpolateString(value, substitutions)
93 return value 81 return value
94 82
95 83
96 def Interpolate(plist, substitutions):
97 """Interpolates variable references into |value| using |substitutions|.
98
99 Inputs:
100 plist: a dictionary representing a Property List (.plist) file
101 substitutions: a mapping of variable names to values
102
103 Returns:
104 A new plist with all variables references ${VARIABLES} replaced by their
105 value in |substitutions|. All values that contains references with no
106 substitutions will be removed and the corresponding key will be cleared
107 from the plist (not recursively).
108 """
109 result = {}
110 for key in plist:
111 value = InterpolateValue(plist[key], substitutions)
112 if value is not None:
113 result[key] = value
114 return result
115
116
117 def LoadPList(path): 84 def LoadPList(path):
118 """Loads Plist at |path| and returns it as a dictionary.""" 85 """Loads Plist at |path| and returns it as a dictionary."""
119 fd, name = tempfile.mkstemp() 86 fd, name = tempfile.mkstemp()
120 try: 87 try:
121 subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path]) 88 subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path])
122 with os.fdopen(fd, 'r') as f: 89 with os.fdopen(fd, 'r') as f:
123 return plistlib.readPlist(f) 90 return plistlib.readPlist(f)
124 finally: 91 finally:
125 os.unlink(name) 92 os.unlink(name)
126 93
(...skipping 19 matching lines...) Expand all
146 Args: 113 Args:
147 plist1: a dictionary representing a Property List (.plist) file 114 plist1: a dictionary representing a Property List (.plist) file
148 plist2: a dictionary representing a Property List (.plist) file 115 plist2: a dictionary representing a Property List (.plist) file
149 116
150 Returns: 117 Returns:
151 A new dictionary representing a Property List (.plist) file by merging 118 A new dictionary representing a Property List (.plist) file by merging
152 |plist1| with |plist2|. If any value is a dictionary, they are merged 119 |plist1| with |plist2|. If any value is a dictionary, they are merged
153 recursively, otherwise |plist2| value is used. If values are list, they 120 recursively, otherwise |plist2| value is used. If values are list, they
154 are concatenated. 121 are concatenated.
155 """ 122 """
156 if not isinstance(plist1, dict) or not isinstance(plist2, dict): 123 result = plist1.copy()
157 if plist2 is not None: 124 for key, value in plist2.iteritems():
158 return plist2
159 else:
160 return plist1
161 result = {}
162 for key in set(plist1) | set(plist2):
163 if key in plist2:
164 value = plist2[key]
165 else:
166 value = plist1[key]
167 if isinstance(value, dict): 125 if isinstance(value, dict):
168 value = MergePList(plist1.get(key, None), plist2.get(key, None)) 126 old_value = result.get(key)
127 if isinstance(old_value, dict):
128 value = MergePList(old_value, value)
169 if isinstance(value, list): 129 if isinstance(value, list):
170 value = plist1.get(key, []) + plist2.get(key, []) 130 value = plist1.get(key, []) + plist2.get(key, [])
171 result[key] = value 131 result[key] = value
172 return result 132 return result
173 133
174 134
175 class Action(object): 135 class Action(object):
176 """Class implementing one action supported by the script.""" 136 """Class implementing one action supported by the script."""
177 137
178 @classmethod 138 @classmethod
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
245 205
246 for action in [MergeAction, SubstituteAction]: 206 for action in [MergeAction, SubstituteAction]:
247 action.Register(subparsers) 207 action.Register(subparsers)
248 208
249 args = parser.parse_args() 209 args = parser.parse_args()
250 args.func(args) 210 args.func(args)
251 211
252 212
253 if __name__ == '__main__': 213 if __name__ == '__main__':
254 sys.exit(Main()) 214 sys.exit(Main())
OLDNEW
« no previous file with comments | « build/config/mac/BuildInfo.plist ('k') | ui/base/BUILD.gn » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698