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