Commit 9e5dcbc6 authored by Julien Muchembled's avatar Julien Muchembled

Be more verbose for the first part to reinstall (changed option or missing path)

Sometimes, most parts are reinstalled for a reason that the user
didn't think about and it can take time to understand why.
Explaining for all parts would be too verbose and useless because
many are reinstalled just because their dependencies changed.
parent a2bcbf2d
......@@ -716,8 +716,25 @@ class Buildout(DictMixin):
# uninstall parts that are no-longer used or who's configs
# have changed
if self._logger.getEffectiveLevel() < logging.DEBUG:
reinstall_reason_score = -1
elif int(os.getenv('BUILDOUT_INFO_REINSTALL_REASON') or 1):
# We rely on the fact that installed_parts is sorted according to
# dependencies (unless install_args). This is not the case of
# installed_parts.
reinstall_reason_score = len(installed_parts)
# Provide a way to disable in tests
# or we'd have to update all recipe eggs.
reinstall_reason_score = 0
reinstall_reason = None
for part in reversed(installed_parts):
if part in install_parts:
part_index = install_parts.index(part)
except ValueError:
if not uninstall_missing:
old_options = installed_part_options[part].copy()
installed_files = old_options.pop('__buildout_installed__')
new_options = self.get(part).copy()
......@@ -740,35 +757,29 @@ class Buildout(DictMixin):
# reinstall.
if not installed_files:
for f in installed_files.split('\n'):
if not os.path.exists(self._buildout_path(f)):
for installed_path in installed_files.split('\n'):
if not os.path.exists(
installed_path = None
# output debugging info
if self._logger.getEffectiveLevel() < logging.DEBUG:
for k in old_options:
if k not in new_options:
self._logger.debug("Part %s, dropped option %s.",
part, k)
elif old_options[k] != new_options[k]:
"Part %s, option %s changed:\n%r != %r",
part, k, new_options[k], old_options[k],
for k in new_options:
if k not in old_options:
self._logger.debug("Part %s, new option %s.",
part, k)
elif not uninstall_missing:
if part_index < reinstall_reason_score:
reinstall_reason_score = part_index
reinstall_reason = (
part, old_options, new_options, installed_path)
elif reinstall_reason_score < 0:
part, old_options, new_options, installed_path)
self._uninstall_part(part, installed_part_options)
installed_parts = [p for p in installed_parts if p != part]
installed_part_options['buildout']['parts'] = (
' '.join(installed_parts))
if reinstall_reason:
self._log_reinstall_reason(logging.INFO, *reinstall_reason)
# Check for unused buildout options:
_check_for_unused_options_in_section(self, 'buildout')
......@@ -854,6 +865,22 @@ class Buildout(DictMixin):
if self._log_level < logging.INFO:
def _log_reinstall_reason(self, level, part,
old_options, new_options, missing):
log = self._logger.log
if missing:
log(level, "Part %s, missing path: %s", part, missing)
for k in old_options:
if k not in new_options:
log(level, "Part %s, dropped option %s.", part, k)
elif old_options[k] != new_options[k]:
log(level, "Part %s, option %s changed: %r != %r",
part, k, new_options[k], old_options[k])
for k in new_options:
if k not in old_options:
log(level, "Part %s, new option %s.", part, k)
def _uninstall_part(self, part, installed_part_options):
# uninstall part
__doing__ = 'Uninstalling %s.', part
......@@ -559,3 +559,5 @@ easy_install_deprecated = (
'WARNING: The easy_install command is deprecated and will be removed in a future version.\n'
), '')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment