Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lifecycle: outdated step should raise SnapcraftError #1513

Merged
merged 3 commits into from Aug 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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