123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- #!/usr/bin/env python
- # encoding: utf-8
- # Laurent Birtz, 2011
- # moved the code into a separate tool (ita)
- """
- There are several things here:
- - a different command-line option management making options persistent
- - the review command to display the options set
- Assumptions:
- - configuration options are not always added to the right group (and do not count on the users to do it...)
- - the options are persistent between the executions (waf options are NOT persistent by design), even for the configuration
- - when the options change, the build is invalidated (forcing a reconfiguration)
- """
- import os, textwrap, shutil
- from waflib import Logs, Context, ConfigSet, Options, Build, Configure
- class Odict(dict):
- """Ordered dictionary"""
- def __init__(self, data=None):
- self._keys = []
- dict.__init__(self)
- if data:
- # we were provided a regular dict
- if isinstance(data, dict):
- self.append_from_dict(data)
- # we were provided a tuple list
- elif type(data) == list:
- self.append_from_plist(data)
- # we were provided invalid input
- else:
- raise Exception("expected a dict or a tuple list")
- def append_from_dict(self, dict):
- map(self.__setitem__, dict.keys(), dict.values())
- def append_from_plist(self, plist):
- for pair in plist:
- if len(pair) != 2:
- raise Exception("invalid pairs list")
- for (k, v) in plist:
- self.__setitem__(k, v)
- def __delitem__(self, key):
- if not key in self._keys:
- raise KeyError(key)
- dict.__delitem__(self, key)
- self._keys.remove(key)
- def __setitem__(self, key, item):
- dict.__setitem__(self, key, item)
- if key not in self._keys:
- self._keys.append(key)
- def clear(self):
- dict.clear(self)
- self._keys = []
- def copy(self):
- return Odict(self.plist())
- def items(self):
- return zip(self._keys, self.values())
- def keys(self):
- return list(self._keys) # return a copy of the list
- def values(self):
- return map(self.get, self._keys)
- def plist(self):
- p = []
- for k, v in self.items():
- p.append( (k, v) )
- return p
- def __str__(self):
- buf = []
- buf.append("{ ")
- for k, v in self.items():
- buf.append('%r : %r, ' % (k, v))
- buf.append("}")
- return ''.join(buf)
- review_options = Odict()
- """
- Ordered dictionary mapping configuration option names to their optparse option.
- """
- review_defaults = {}
- """
- Dictionary mapping configuration option names to their default value.
- """
- old_review_set = None
- """
- Review set containing the configuration values before parsing the command line.
- """
- new_review_set = None
- """
- Review set containing the configuration values after parsing the command line.
- """
- class OptionsReview(Options.OptionsContext):
- def __init__(self, **kw):
- super(self.__class__, self).__init__(**kw)
- def prepare_config_review(self):
- """
- Find the configuration options that are reviewable, detach
- their default value from their optparse object and store them
- into the review dictionaries.
- """
- gr = self.get_option_group('configure options')
- for opt in gr.option_list:
- if opt.action != 'store' or opt.dest in ("out", "top"):
- continue
- review_options[opt.dest] = opt
- review_defaults[opt.dest] = opt.default
- if gr.defaults.has_key(opt.dest):
- del gr.defaults[opt.dest]
- opt.default = None
- def parse_args(self):
- self.prepare_config_review()
- self.parser.get_option('--prefix').help = 'installation prefix'
- super(OptionsReview, self).parse_args()
- Context.create_context('review').refresh_review_set()
- class ReviewContext(Context.Context):
- '''reviews the configuration values'''
- cmd = 'review'
- def __init__(self, **kw):
- super(self.__class__, self).__init__(**kw)
- out = Options.options.out
- if not out:
- out = getattr(Context.g_module, Context.OUT, None)
- if not out:
- out = Options.lockfile.replace('.lock-waf', '')
- self.build_path = (os.path.isabs(out) and self.root or self.path).make_node(out).abspath()
- """Path to the build directory"""
- self.cache_path = os.path.join(self.build_path, Build.CACHE_DIR)
- """Path to the cache directory"""
- self.review_path = os.path.join(self.cache_path, 'review.cache')
- """Path to the review cache file"""
- def execute(self):
- """
- Display and store the review set. Invalidate the cache as required.
- """
- if not self.compare_review_set(old_review_set, new_review_set):
- self.invalidate_cache()
- self.store_review_set(new_review_set)
- print(self.display_review_set(new_review_set))
- def invalidate_cache(self):
- """Invalidate the cache to prevent bad builds."""
- try:
- Logs.warn("Removing the cached configuration since the options have changed")
- shutil.rmtree(self.cache_path)
- except:
- pass
- def refresh_review_set(self):
- """
- Obtain the old review set and the new review set, and import the new set.
- """
- global old_review_set, new_review_set
- old_review_set = self.load_review_set()
- new_review_set = self.update_review_set(old_review_set)
- self.import_review_set(new_review_set)
- def load_review_set(self):
- """
- Load and return the review set from the cache if it exists.
- Otherwise, return an empty set.
- """
- if os.path.isfile(self.review_path):
- return ConfigSet.ConfigSet(self.review_path)
- return ConfigSet.ConfigSet()
- def store_review_set(self, review_set):
- """
- Store the review set specified in the cache.
- """
- if not os.path.isdir(self.cache_path):
- os.makedirs(self.cache_path)
- review_set.store(self.review_path)
- def update_review_set(self, old_set):
- """
- Merge the options passed on the command line with those imported
- from the previous review set and return the corresponding
- preview set.
- """
- # Convert value to string. It's important that 'None' maps to
- # the empty string.
- def val_to_str(val):
- if val == None or val == '':
- return ''
- return str(val)
- new_set = ConfigSet.ConfigSet()
- opt_dict = Options.options.__dict__
- for name in review_options.keys():
- # the option is specified explicitly on the command line
- if name in opt_dict:
- # if the option is the default, pretend it was never specified
- if val_to_str(opt_dict[name]) != val_to_str(review_defaults[name]):
- new_set[name] = opt_dict[name]
- # the option was explicitly specified in a previous command
- elif name in old_set:
- new_set[name] = old_set[name]
- return new_set
- def import_review_set(self, review_set):
- """
- Import the actual value of the reviewable options in the option
- dictionary, given the current review set.
- """
- for name in review_options.keys():
- if name in review_set:
- value = review_set[name]
- else:
- value = review_defaults[name]
- setattr(Options.options, name, value)
- def compare_review_set(self, set1, set2):
- """
- Return true if the review sets specified are equal.
- """
- if len(set1.keys()) != len(set2.keys()):
- return False
- for key in set1.keys():
- if not key in set2 or set1[key] != set2[key]:
- return False
- return True
- def display_review_set(self, review_set):
- """
- Return the string representing the review set specified.
- """
- term_width = Logs.get_term_cols()
- lines = []
- for dest in review_options.keys():
- opt = review_options[dest]
- name = ", ".join(opt._short_opts + opt._long_opts)
- help = opt.help
- actual = None
- if dest in review_set:
- actual = review_set[dest]
- default = review_defaults[dest]
- lines.append(self.format_option(name, help, actual, default, term_width))
- return "Configuration:\n\n" + "\n\n".join(lines) + "\n"
- def format_option(self, name, help, actual, default, term_width):
- """
- Return the string representing the option specified.
- """
- def val_to_str(val):
- if val == None or val == '':
- return "(void)"
- return str(val)
- max_name_len = 20
- sep_len = 2
- w = textwrap.TextWrapper()
- w.width = term_width - 1
- if w.width < 60:
- w.width = 60
- out = ""
- # format the help
- out += w.fill(help) + "\n"
- # format the name
- name_len = len(name)
- out += Logs.colors.CYAN + name + Logs.colors.NORMAL
- # set the indentation used when the value wraps to the next line
- w.subsequent_indent = " ".rjust(max_name_len + sep_len)
- w.width -= (max_name_len + sep_len)
- # the name string is too long, switch to the next line
- if name_len > max_name_len:
- out += "\n" + w.subsequent_indent
- # fill the remaining of the line with spaces
- else:
- out += " ".rjust(max_name_len + sep_len - name_len)
- # format the actual value, if there is one
- if actual != None:
- out += Logs.colors.BOLD + w.fill(val_to_str(actual)) + Logs.colors.NORMAL + "\n" + w.subsequent_indent
- # format the default value
- default_fmt = val_to_str(default)
- if actual != None:
- default_fmt = "default: " + default_fmt
- out += Logs.colors.NORMAL + w.fill(default_fmt) + Logs.colors.NORMAL
- return out
- # Monkey-patch ConfigurationContext.execute() to have it store the review set.
- old_configure_execute = Configure.ConfigurationContext.execute
- def new_configure_execute(self):
- old_configure_execute(self)
- Context.create_context('review').store_review_set(new_review_set)
- Configure.ConfigurationContext.execute = new_configure_execute
|