Package buildbot :: Package process :: Module properties
[frames] | no frames]

Source Code for Module buildbot.process.properties

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  import re 
 17  import weakref 
 18  from buildbot import util 
 19   
20 -class Properties(util.ComparableMixin):
21 """ 22 I represent a set of properties that can be interpolated into various 23 strings in buildsteps. 24 25 @ivar properties: dictionary mapping property values to tuples 26 (value, source), where source is a string identifing the source 27 of the property. 28 29 Objects of this class can be read like a dictionary -- in this case, 30 only the property value is returned. 31 32 As a special case, a property value of None is returned as an empty 33 string when used as a mapping. 34 """ 35 36 compare_attrs = ('properties',) 37
38 - def __init__(self, **kwargs):
39 """ 40 @param kwargs: initial property values (for testing) 41 """ 42 self.properties = {} 43 # Track keys which are 'runtime', and should not be 44 # persisted if a build is rebuilt 45 self.runtime = set() 46 self.pmap = PropertyMap(self) 47 if kwargs: self.update(kwargs, "TEST")
48
49 - def __getstate__(self):
50 d = self.__dict__.copy() 51 del d['pmap'] 52 return d
53
54 - def __setstate__(self, d):
55 self.__dict__ = d 56 self.pmap = PropertyMap(self) 57 if not hasattr(self, 'runtime'): 58 self.runtime = set()
59
60 - def __contains__(self, name):
61 return name in self.properties
62
63 - def __getitem__(self, name):
64 """Just get the value for this property.""" 65 rv = self.properties[name][0] 66 return rv
67
68 - def has_key(self, name):
69 return self.properties.has_key(name)
70
71 - def getProperty(self, name, default=None):
72 """Get the value for the given property.""" 73 return self.properties.get(name, (default,))[0]
74
75 - def getPropertySource(self, name):
76 return self.properties[name][1]
77
78 - def asList(self):
79 """Return the properties as a sorted list of (name, value, source)""" 80 l = [ (k, v[0], v[1]) for k,v in self.properties.items() ] 81 l.sort() 82 return l
83
84 - def __repr__(self):
85 return repr(dict([ (k,v[0]) for k,v in self.properties.iteritems() ]))
86
87 - def setProperty(self, name, value, source, runtime=False):
88 self.properties[name] = (value, source) 89 if runtime: 90 self.runtime.add(name)
91
92 - def update(self, dict, source, runtime=False):
93 """Update this object from a dictionary, with an explicit source specified.""" 94 for k, v in dict.items(): 95 self.properties[k] = (v, source) 96 if runtime: 97 self.runtime.add(k)
98
99 - def updateFromProperties(self, other):
100 """Update this object based on another object; the other object's """ 101 self.properties.update(other.properties) 102 self.runtime.update(other.runtime)
103
104 - def updateFromPropertiesNoRuntime(self, other):
105 """Update this object based on another object, but don't 106 include properties that were marked as runtime.""" 107 for k,v in other.properties.iteritems(): 108 if k not in other.runtime: 109 self.properties[k] = v
110
111 - def render(self, value):
112 """ 113 Return a variant of value that has any WithProperties objects 114 substituted. This recurses into Python's compound data types. 115 """ 116 # we use isinstance to detect Python's standard data types, and call 117 # this function recursively for the values in those types 118 if isinstance(value, (str, unicode)): 119 return value 120 elif isinstance(value, WithProperties): 121 return value.render(self.pmap) 122 elif isinstance(value, list): 123 return [ self.render(e) for e in value ] 124 elif isinstance(value, tuple): 125 return tuple([ self.render(e) for e in value ]) 126 elif isinstance(value, dict): 127 return dict([ (self.render(k), self.render(v)) for k,v in value.iteritems() ]) 128 else: 129 return value
130
131 -class PropertyMap:
132 """ 133 Privately-used mapping object to implement WithProperties' substitutions, 134 including the rendering of None as ''. 135 """ 136 colon_minus_re = re.compile(r"(.*):-(.*)") 137 colon_tilde_re = re.compile(r"(.*):~(.*)") 138 colon_plus_re = re.compile(r"(.*):\+(.*)")
139 - def __init__(self, properties):
140 # use weakref here to avoid a reference loop 141 self.properties = weakref.ref(properties) 142 self.temp_vals = {}
143
144 - def __getitem__(self, key):
145 properties = self.properties() 146 assert properties is not None 147 148 def colon_minus(mo): 149 # %(prop:-repl)s 150 # if prop exists, use it; otherwise, use repl 151 prop, repl = mo.group(1,2) 152 if prop in self.temp_vals: 153 return self.temp_vals[prop] 154 elif properties.has_key(prop): 155 return properties[prop] 156 else: 157 return repl
158 159 def colon_tilde(mo): 160 # %(prop:~repl)s 161 # if prop exists and is true (nonempty), use it; otherwise, use repl 162 prop, repl = mo.group(1,2) 163 if prop in self.temp_vals and self.temp_vals[prop]: 164 return self.temp_vals[prop] 165 elif properties.has_key(prop) and properties[prop]: 166 return properties[prop] 167 else: 168 return repl
169 170 def colon_plus(mo): 171 # %(prop:+repl)s 172 # if prop exists, use repl; otherwise, an empty string 173 prop, repl = mo.group(1,2) 174 if properties.has_key(prop) or prop in self.temp_vals: 175 return repl 176 else: 177 return '' 178 179 for regexp, fn in [ 180 ( self.colon_minus_re, colon_minus ), 181 ( self.colon_tilde_re, colon_tilde ), 182 ( self.colon_plus_re, colon_plus ), 183 ]: 184 mo = regexp.match(key) 185 if mo: 186 rv = fn(mo) 187 break 188 else: 189 # If explicitly passed as a kwarg, use that, 190 # otherwise, use the property value. 191 if key in self.temp_vals: 192 rv = self.temp_vals[key] 193 else: 194 rv = properties[key] 195 196 # translate 'None' to an empty string 197 if rv is None: rv = '' 198 return rv 199
200 - def add_temporary_value(self, key, val):
201 'Add a temporary value (to support keyword arguments to WithProperties)' 202 self.temp_vals[key] = val
203
204 - def clear_temporary_values(self):
205 self.temp_vals = {}
206
207 -class WithProperties(util.ComparableMixin):
208 """ 209 This is a marker class, used fairly widely to indicate that we 210 want to interpolate build properties. 211 """ 212 213 compare_attrs = ('fmtstring', 'args') 214
215 - def __init__(self, fmtstring, *args, **lambda_subs):
216 self.fmtstring = fmtstring 217 self.args = args 218 if not self.args: 219 self.lambda_subs = lambda_subs 220 for key, val in self.lambda_subs.iteritems(): 221 if not callable(val): 222 raise ValueError('Value for lambda substitution "%s" must be callable.' % key) 223 elif lambda_subs: 224 raise ValueError('WithProperties takes either positional or keyword substitutions, not both.')
225
226 - def render(self, pmap):
227 if self.args: 228 strings = [] 229 for name in self.args: 230 strings.append(pmap[name]) 231 s = self.fmtstring % tuple(strings) 232 else: 233 properties = pmap.properties() 234 for k,v in self.lambda_subs.iteritems(): 235 pmap.add_temporary_value(k, v(properties)) 236 s = self.fmtstring % pmap 237 pmap.clear_temporary_values() 238 return s
239