ctdb-scripts: Support storing statd-callout state in cluster filesystem
[samba4-gss.git] / third_party / waf / waflib / extras / review.py
blob561e06219da1d52ca00e7a461e39df02548798d0
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Laurent Birtz, 2011
4 # moved the code into a separate tool (ita)
6 """
7 There are several things here:
8 - a different command-line option management making options persistent
9 - the review command to display the options set
11 Assumptions:
12 - configuration options are not always added to the right group (and do not count on the users to do it...)
13 - the options are persistent between the executions (waf options are NOT persistent by design), even for the configuration
14 - when the options change, the build is invalidated (forcing a reconfiguration)
15 """
17 import os, textwrap, shutil
18 from waflib import Logs, Context, ConfigSet, Options, Build, Configure
20 class Odict(dict):
21 """Ordered dictionary"""
22 def __init__(self, data=None):
23 self._keys = []
24 dict.__init__(self)
25 if data:
26 # we were provided a regular dict
27 if isinstance(data, dict):
28 self.append_from_dict(data)
30 # we were provided a tuple list
31 elif type(data) == list:
32 self.append_from_plist(data)
34 # we were provided invalid input
35 else:
36 raise Exception("expected a dict or a tuple list")
38 def append_from_dict(self, dict):
39 map(self.__setitem__, dict.keys(), dict.values())
41 def append_from_plist(self, plist):
42 for pair in plist:
43 if len(pair) != 2:
44 raise Exception("invalid pairs list")
45 for (k, v) in plist:
46 self.__setitem__(k, v)
48 def __delitem__(self, key):
49 if not key in self._keys:
50 raise KeyError(key)
51 dict.__delitem__(self, key)
52 self._keys.remove(key)
54 def __setitem__(self, key, item):
55 dict.__setitem__(self, key, item)
56 if key not in self._keys:
57 self._keys.append(key)
59 def clear(self):
60 dict.clear(self)
61 self._keys = []
63 def copy(self):
64 return Odict(self.plist())
66 def items(self):
67 return zip(self._keys, self.values())
69 def keys(self):
70 return list(self._keys) # return a copy of the list
72 def values(self):
73 return map(self.get, self._keys)
75 def plist(self):
76 p = []
77 for k, v in self.items():
78 p.append( (k, v) )
79 return p
81 def __str__(self):
82 buf = []
83 buf.append("{ ")
84 for k, v in self.items():
85 buf.append('%r : %r, ' % (k, v))
86 buf.append("}")
87 return ''.join(buf)
89 review_options = Odict()
90 """
91 Ordered dictionary mapping configuration option names to their optparse option.
92 """
94 review_defaults = {}
95 """
96 Dictionary mapping configuration option names to their default value.
97 """
99 old_review_set = None
101 Review set containing the configuration values before parsing the command line.
104 new_review_set = None
106 Review set containing the configuration values after parsing the command line.
109 class OptionsReview(Options.OptionsContext):
110 def __init__(self, **kw):
111 super(self.__class__, self).__init__(**kw)
113 def prepare_config_review(self):
115 Find the configuration options that are reviewable, detach
116 their default value from their optparse object and store them
117 into the review dictionaries.
119 gr = self.get_option_group('configure options')
120 for opt in gr.option_list:
121 if opt.action != 'store' or opt.dest in ("out", "top"):
122 continue
123 review_options[opt.dest] = opt
124 review_defaults[opt.dest] = opt.default
125 if gr.defaults.has_key(opt.dest):
126 del gr.defaults[opt.dest]
127 opt.default = None
129 def parse_args(self):
130 self.prepare_config_review()
131 self.parser.get_option('--prefix').help = 'installation prefix'
132 super(OptionsReview, self).parse_args()
133 Context.create_context('review').refresh_review_set()
135 class ReviewContext(Context.Context):
136 '''reviews the configuration values'''
138 cmd = 'review'
140 def __init__(self, **kw):
141 super(self.__class__, self).__init__(**kw)
143 out = Options.options.out
144 if not out:
145 out = getattr(Context.g_module, Context.OUT, None)
146 if not out:
147 out = Options.lockfile.replace('.lock-waf', '')
148 self.build_path = (os.path.isabs(out) and self.root or self.path).make_node(out).abspath()
149 """Path to the build directory"""
151 self.cache_path = os.path.join(self.build_path, Build.CACHE_DIR)
152 """Path to the cache directory"""
154 self.review_path = os.path.join(self.cache_path, 'review.cache')
155 """Path to the review cache file"""
157 def execute(self):
159 Display and store the review set. Invalidate the cache as required.
161 if not self.compare_review_set(old_review_set, new_review_set):
162 self.invalidate_cache()
163 self.store_review_set(new_review_set)
164 print(self.display_review_set(new_review_set))
166 def invalidate_cache(self):
167 """Invalidate the cache to prevent bad builds."""
168 try:
169 Logs.warn("Removing the cached configuration since the options have changed")
170 shutil.rmtree(self.cache_path)
171 except:
172 pass
174 def refresh_review_set(self):
176 Obtain the old review set and the new review set, and import the new set.
178 global old_review_set, new_review_set
179 old_review_set = self.load_review_set()
180 new_review_set = self.update_review_set(old_review_set)
181 self.import_review_set(new_review_set)
183 def load_review_set(self):
185 Load and return the review set from the cache if it exists.
186 Otherwise, return an empty set.
188 if os.path.isfile(self.review_path):
189 return ConfigSet.ConfigSet(self.review_path)
190 return ConfigSet.ConfigSet()
192 def store_review_set(self, review_set):
194 Store the review set specified in the cache.
196 if not os.path.isdir(self.cache_path):
197 os.makedirs(self.cache_path)
198 review_set.store(self.review_path)
200 def update_review_set(self, old_set):
202 Merge the options passed on the command line with those imported
203 from the previous review set and return the corresponding
204 preview set.
207 # Convert value to string. It's important that 'None' maps to
208 # the empty string.
209 def val_to_str(val):
210 if val == None or val == '':
211 return ''
212 return str(val)
214 new_set = ConfigSet.ConfigSet()
215 opt_dict = Options.options.__dict__
217 for name in review_options.keys():
218 # the option is specified explicitly on the command line
219 if name in opt_dict:
220 # if the option is the default, pretend it was never specified
221 if val_to_str(opt_dict[name]) != val_to_str(review_defaults[name]):
222 new_set[name] = opt_dict[name]
223 # the option was explicitly specified in a previous command
224 elif name in old_set:
225 new_set[name] = old_set[name]
227 return new_set
229 def import_review_set(self, review_set):
231 Import the actual value of the reviewable options in the option
232 dictionary, given the current review set.
234 for name in review_options.keys():
235 if name in review_set:
236 value = review_set[name]
237 else:
238 value = review_defaults[name]
239 setattr(Options.options, name, value)
241 def compare_review_set(self, set1, set2):
243 Return true if the review sets specified are equal.
245 if len(set1.keys()) != len(set2.keys()):
246 return False
247 for key in set1.keys():
248 if not key in set2 or set1[key] != set2[key]:
249 return False
250 return True
252 def display_review_set(self, review_set):
254 Return the string representing the review set specified.
256 term_width = Logs.get_term_cols()
257 lines = []
258 for dest in review_options.keys():
259 opt = review_options[dest]
260 name = ", ".join(opt._short_opts + opt._long_opts)
261 help = opt.help
262 actual = None
263 if dest in review_set:
264 actual = review_set[dest]
265 default = review_defaults[dest]
266 lines.append(self.format_option(name, help, actual, default, term_width))
267 return "Configuration:\n\n" + "\n\n".join(lines) + "\n"
269 def format_option(self, name, help, actual, default, term_width):
271 Return the string representing the option specified.
273 def val_to_str(val):
274 if val == None or val == '':
275 return "(void)"
276 return str(val)
278 max_name_len = 20
279 sep_len = 2
281 w = textwrap.TextWrapper()
282 w.width = term_width - 1
283 if w.width < 60:
284 w.width = 60
286 out = ""
288 # format the help
289 out += w.fill(help) + "\n"
291 # format the name
292 name_len = len(name)
293 out += Logs.colors.CYAN + name + Logs.colors.NORMAL
295 # set the indentation used when the value wraps to the next line
296 w.subsequent_indent = " ".rjust(max_name_len + sep_len)
297 w.width -= (max_name_len + sep_len)
299 # the name string is too long, switch to the next line
300 if name_len > max_name_len:
301 out += "\n" + w.subsequent_indent
303 # fill the remaining of the line with spaces
304 else:
305 out += " ".rjust(max_name_len + sep_len - name_len)
307 # format the actual value, if there is one
308 if actual != None:
309 out += Logs.colors.BOLD + w.fill(val_to_str(actual)) + Logs.colors.NORMAL + "\n" + w.subsequent_indent
311 # format the default value
312 default_fmt = val_to_str(default)
313 if actual != None:
314 default_fmt = "default: " + default_fmt
315 out += Logs.colors.NORMAL + w.fill(default_fmt) + Logs.colors.NORMAL
317 return out
319 # Monkey-patch ConfigurationContext.execute() to have it store the review set.
320 old_configure_execute = Configure.ConfigurationContext.execute
321 def new_configure_execute(self):
322 old_configure_execute(self)
323 Context.create_context('review').store_review_set(new_review_set)
324 Configure.ConfigurationContext.execute = new_configure_execute