Skip to content

Commit

Permalink
lifecycle: outdated step should raise SnapcraftError (#1513)
Browse files Browse the repository at this point in the history
  • Loading branch information
kalikiana authored and sergiusens committed Aug 29, 2017
1 parent af16b80 commit 141d7fd
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 49 deletions.
53 changes: 53 additions & 0 deletions snapcraft/internal/errors.py
Expand Up @@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from snapcraft import formatting_utils


class SnapcraftError(Exception):
"""Base class for all snapcraft exceptions.
Expand Down Expand Up @@ -45,6 +47,57 @@ def __init__(self, step):
super().__init__(step=step)


class StepOutdatedError(SnapcraftError):

fmt = (
'The {step!r} step of {part!r} is out of date:\n'
'{report}'
'In order to continue, please clean that part\'s {step!r} step '
'by running:\n'
'snapcraft clean {parts_names} -s {step}\n'
)

def __init__(self, *, step, part,
dirty_properties=None, dirty_project_options=None,
dependents=None):
messages = []
if dirty_properties:
humanized_properties = formatting_utils.humanize_list(
dirty_properties, 'and')
pluralized_connection = formatting_utils.pluralize(
dirty_properties, 'property appears',
'properties appear')
messages.append(
'The {} part {} to have changed.\n'.format(
humanized_properties, pluralized_connection))
if dirty_project_options:
humanized_options = formatting_utils.humanize_list(
dirty_project_options, 'and')
pluralized_connection = formatting_utils.pluralize(
dirty_project_options, 'option appears',
'options appear')
messages.append(
'The {} project {} to have changed.\n'.format(
humanized_options, pluralized_connection))
if dependents:
humanized_dependents = formatting_utils.humanize_list(
dependents, 'and')
pluralized_dependents = formatting_utils.pluralize(
dependents, "depends", "depend")
messages.append('The {0!r} step for {1!r} needs to be run again, '
'but {2} {3} on it.\n'.format(
step,
part,
humanized_dependents,
pluralized_dependents))
parts_names = ['{!s}'.format(d) for d in sorted(dependents)]
else:
parts_names = [part]
super().__init__(step=step, part=part,
report=''.join(messages),
parts_names=' '.join(parts_names))


class SnapcraftEnvironmentError(SnapcraftError):
fmt = '{message}'

Expand Down
46 changes: 6 additions & 40 deletions snapcraft/internal/lifecycle.py
Expand Up @@ -267,35 +267,10 @@ def _create_meta(self, step, part_names):

def _handle_dirty(self, part, step, dirty_report):
if step not in _STEPS_TO_AUTOMATICALLY_CLEAN_IF_DIRTY:
message_components = [
'The {!r} step of {!r} is out of date:\n'.format(
step, part.name)]

if dirty_report.dirty_properties:
humanized_properties = formatting_utils.humanize_list(
dirty_report.dirty_properties, 'and')
pluralized_connection = formatting_utils.pluralize(
dirty_report.dirty_properties, 'property appears',
'properties appear')
message_components.append(
'The {} part {} to have changed.\n'.format(
humanized_properties, pluralized_connection))

if dirty_report.dirty_project_options:
humanized_options = formatting_utils.humanize_list(
dirty_report.dirty_project_options, 'and')
pluralized_connection = formatting_utils.pluralize(
dirty_report.dirty_project_options, 'option appears',
'options appear')
message_components.append(
'The {} project {} to have changed.\n'.format(
humanized_options, pluralized_connection))

message_components.append(
"In order to continue, please clean that part's {0!r} step "
"by running: snapcraft clean {1} -s {0}\n".format(
step, part.name))
raise RuntimeError(''.join(message_components))
raise errors.StepOutdatedError(
step=step, part=part.name,
dirty_properties=dirty_report.dirty_properties,
dirty_project_options=dirty_report.dirty_project_options)

staged_state = self.config.get_project_state('stage')
primed_state = self.config.get_project_state('prime')
Expand All @@ -310,17 +285,8 @@ def _handle_dirty(self, part, step, dirty_report):
for dependent in self.config.all_parts:
if (dependent.name in dependents and
not dependent.is_clean('build')):
humanized_parts = formatting_utils.humanize_list(
dependents, 'and')
pluralized_depends = formatting_utils.pluralize(
dependents, "depends", "depend")

raise RuntimeError(
'The {0!r} step for {1!r} needs to be run again, but '
'{2} {3} upon it. Please clean the build '
'step of {2} first.'.format(
step, part.name, humanized_parts,
pluralized_depends))
raise errors.StepOutdatedError(step=step, part=part.name,
dependents=dependents)

part.clean(staged_state, primed_state, step, '(out of date)')

Expand Down
20 changes: 11 additions & 9 deletions snapcraft/tests/test_lifecycle.py
Expand Up @@ -426,7 +426,7 @@ def _fake_dirty_report(self, step):
with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report',
_fake_dirty_report):
raised = self.assertRaises(
RuntimeError,
errors.StepOutdatedError,
lifecycle.execute,
'stage', self.project_options,
part_names=['part1'])
Expand All @@ -442,9 +442,11 @@ def _fake_dirty_report(self, step):

self.assertThat(
str(raised), Equals(
"The 'stage' step of 'part1' is out of date:\n"
"The 'stage' step for 'part1' needs to be run again, but "
"'part2' depends upon it. Please clean the build step of "
"'part2' first."))
"'part2' depends on it.\n"
"In order to continue, please clean that part's 'stage' step "
"by running:\nsnapcraft clean part2 -s stage\n"))

def test_dirty_stage_part_with_unbuilt_dependent(self):
self.make_snapcraft_yaml("""parts:
Expand Down Expand Up @@ -542,7 +544,7 @@ def _fake_dirty_report(self, step):
with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report',
_fake_dirty_report):
raised = self.assertRaises(
RuntimeError,
errors.StepOutdatedError,
lifecycle.execute,
'build', self.project_options)

Expand All @@ -555,7 +557,7 @@ def _fake_dirty_report(self, step):
"The 'build' step of 'part1' is out of date:\n"
"The 'bar' and 'foo' part properties appear to have changed.\n"
"In order to continue, please clean that part's 'build' step "
"by running: snapcraft clean part1 -s build\n"))
"by running:\nsnapcraft clean part1 -s build\n"))

def test_dirty_pull_raises(self):
self.make_snapcraft_yaml("""parts:
Expand All @@ -579,7 +581,7 @@ def _fake_dirty_report(self, step):
with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report',
_fake_dirty_report):
raised = self.assertRaises(
RuntimeError,
errors.StepOutdatedError,
lifecycle.execute,
'pull', self.project_options)

Expand All @@ -590,7 +592,7 @@ def _fake_dirty_report(self, step):
"The 'pull' step of 'part1' is out of date:\n"
"The 'bar' and 'foo' project options appear to have changed.\n"
"In order to continue, please clean that part's 'pull' step "
"by running: snapcraft clean part1 -s pull\n"))
"by running:\nsnapcraft clean part1 -s pull\n"))

@mock.patch.object(snapcraft.BasePlugin, 'enable_cross_compilation')
@mock.patch('snapcraft.repo.Repo.install_build_packages')
Expand All @@ -614,7 +616,7 @@ def test_pull_is_dirty_if_target_arch_changes(
# re-pulled due to the change in target architecture and raise an
# error.
raised = self.assertRaises(
RuntimeError,
errors.StepOutdatedError,
lifecycle.execute,
'pull', snapcraft.ProjectOptions(
target_deb_arch='armhf'))
Expand All @@ -628,7 +630,7 @@ def test_pull_is_dirty_if_target_arch_changes(
"The 'pull' step of 'part1' is out of date:\n"
"The 'deb_arch' project option appears to have changed.\n"
"In order to continue, please clean that part's 'pull' step "
"by running: snapcraft clean part1 -s pull\n"))
"by running:\nsnapcraft clean part1 -s pull\n"))

def test_prime_excludes_internal_snapcraft_dir(self):
self.make_snapcraft_yaml("""parts:
Expand Down

0 comments on commit 141d7fd

Please sign in to comment.