4 # moved the code into a separate tool (ita)
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
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)
17 import os
, textwrap
, shutil
18 from waflib
import Logs
, Context
, ConfigSet
, Options
, Build
, Configure
21 """Ordered dictionary"""
22 def __init__(self
, data
=None):
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
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
):
44 raise Exception("invalid pairs list")
46 self
.__setitem
__(k
, v
)
48 def __delitem__(self
, key
):
49 if not key
in self
._keys
:
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
)
64 return Odict(self
.plist())
67 return zip(self
._keys
, self
.values())
70 return list(self
._keys
) # return a copy of the list
73 return map(self
.get
, self
._keys
)
77 for k
, v
in self
.items():
84 for k
, v
in self
.items():
85 buf
.append('%r : %r, ' % (k
, v
))
89 review_options
= Odict()
91 Ordered dictionary mapping configuration option names to their optparse option.
96 Dictionary mapping configuration option names to their default value.
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"):
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
]
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'''
140 def __init__(self
, **kw
):
141 super(self
.__class
__, self
).__init
__(**kw
)
143 out
= Options
.options
.out
145 out
= getattr(Context
.g_module
, Context
.OUT
, None)
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"""
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."""
169 Logs
.warn("Removing the cached configuration since the options have changed")
170 shutil
.rmtree(self
.cache_path
)
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
207 # Convert value to string. It's important that 'None' maps to
210 if val
== None or 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
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
]
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
]
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()):
247 for key
in set1
.keys():
248 if not key
in set2
or set1
[key
] != set2
[key
]:
252 def display_review_set(self
, review_set
):
254 Return the string representing the review set specified.
256 term_width
= Logs
.get_term_cols()
258 for dest
in review_options
.keys():
259 opt
= review_options
[dest
]
260 name
= ", ".join(opt
._short
_opts
+ opt
._long
_opts
)
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.
274 if val
== None or val
== '':
281 w
= textwrap
.TextWrapper()
282 w
.width
= term_width
- 1
289 out
+= w
.fill(help) + "\n"
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
305 out
+= " ".rjust(max_name_len
+ sep_len
- name_len
)
307 # format the actual value, if there is one
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
)
314 default_fmt
= "default: " + default_fmt
315 out
+= Logs
.colors
.NORMAL
+ w
.fill(default_fmt
) + Logs
.colors
.NORMAL
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