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

many: extract lifecycle ordering into own module #2159

Merged
merged 9 commits into from
Jun 12, 2018
22 changes: 14 additions & 8 deletions snapcraft/cli/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
import click
import os

from snapcraft.internal import deprecations, lifecycle, lxd, project_loader
from snapcraft.internal import (
deprecations,
lifecycle,
lxd,
project_loader,
steps,
)
from ._options import add_build_options, get_project_options
from . import echo
from . import env
Expand Down Expand Up @@ -62,7 +68,7 @@ def pull(ctx, parts, **kwargs):
snapcraft pull my-part1 my-part2

"""
_execute('pull', parts, **kwargs)
_execute(steps.PULL, parts, **kwargs)


@lifecyclecli.command()
Expand All @@ -77,7 +83,7 @@ def build(parts, **kwargs):
snapcraft build my-part1 my-part2

"""
_execute('build', parts, **kwargs)
_execute(steps.BUILD, parts, **kwargs)


@lifecyclecli.command()
Expand All @@ -92,7 +98,7 @@ def stage(parts, **kwargs):
snapcraft stage my-part1 my-part2

"""
_execute('stage', parts, **kwargs)
_execute(steps.STAGE, parts, **kwargs)


@lifecyclecli.command()
Expand All @@ -107,7 +113,7 @@ def prime(parts, **kwargs):
snapcraft prime my-part1 my-part2

"""
_execute('prime', parts, **kwargs)
_execute(steps.PRIME, parts, **kwargs)


@lifecyclecli.command()
Expand Down Expand Up @@ -175,12 +181,12 @@ def clean(parts, step, **kwargs):
project_options = get_project_options(**kwargs)
build_environment = env.BuilderEnvironmentConfig()
if build_environment.is_host:
step = step or 'pull'
step = step or steps.next_step(step).name
if step == 'strip':
echo.warning('DEPRECATED: Use `prime` instead of `strip` '
'as the step to clean')
step = 'prime'
lifecycle.clean(project_options, parts, step)
step = steps.PRIME.name
lifecycle.clean(project_options, parts, steps.Step(step))
else:
config = project_loader.load_config(project_options)
lxd.Project(project_options=project_options,
Expand Down
1 change: 0 additions & 1 deletion snapcraft/internal/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@

SNAPCRAFT_FILES = ['snapcraft.yaml', '.snapcraft.yaml', 'parts', 'stage',
'prime', 'snap']
COMMAND_ORDER = ['pull', 'build', 'stage', 'prime']
_DEFAULT_PLUGINDIR = os.path.join(sys.prefix, 'share', 'snapcraft', 'plugins')
_plugindir = _DEFAULT_PLUGINDIR
_DEFAULT_SCHEMADIR = os.path.join(sys.prefix, 'share', 'snapcraft', 'schema')
Expand Down
24 changes: 19 additions & 5 deletions snapcraft/internal/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def get_exit_code(self):
class MissingStateCleanError(SnapcraftError):
fmt = (
"Failed to clean: "
"Missing state for {step!r}. "
"Missing state for {step.name!r}. "
"To clean the project, run `snapcraft clean`."
)

Expand All @@ -54,10 +54,10 @@ class StepOutdatedError(SnapcraftError):

fmt = (
'Failed to reuse files from previous build: '
'The {step!r} step of {part!r} is out of date:\n'
'The {step.name!r} step of {part!r} is out of date:\n'
'{report}'
'To continue, clean that part\'s {step!r} step, run '
'`snapcraft clean {parts_names} -s {step}`.'
'To continue, clean that part\'s {step.name!r} step, run '
'`snapcraft clean {parts_names} -s {step.name}`.'
)

def __init__(self, *, step, part,
Expand Down Expand Up @@ -89,7 +89,7 @@ def __init__(self, *, step, part,
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,
step.name,
part,
humanized_dependents,
pluralized_dependents))
Expand Down Expand Up @@ -596,6 +596,20 @@ def __init__(self, path):
super().__init__(path=path)


class NoLatestStepError(SnapcraftError):
fmt = "The {part_name!r} part hasn't run any steps"

def __init__(self, part_name):
super().__init__(part_name=part_name)


class NoNextStepError(SnapcraftError):
fmt = '{step.name!r} is the final step'

def __init__(self, step):
super().__init__(step=step)


class MountPointNotFoundError(SnapcraftError):
fmt = (
'Nothing is mounted at {mount_point!r}'
Expand Down
45 changes: 21 additions & 24 deletions snapcraft/internal/lifecycle/_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import shutil

from snapcraft import formatting_utils
from snapcraft.internal import common, errors, project_loader, mountinfo
from snapcraft.internal import errors, project_loader, mountinfo, steps
from . import constants


Expand Down Expand Up @@ -93,27 +93,24 @@ def _remove_directory_if_empty(directory):


def _cleanup_common_directories(config, project_options):
max_index = -1
max_step = None
for part in config.all_parts:
step = part.last_step()
if step:
index = common.COMMAND_ORDER.index(step)
if index > max_index:
max_index = index
with contextlib.suppress(errors.NoLatestStepError):
step = part.latest_step()
if not max_step or step > max_step:
max_step = step

with contextlib.suppress(IndexError):
_cleanup_common_directories_for_step(
common.COMMAND_ORDER[max_index+1], project_options)
with contextlib.suppress(errors.NoNextStepError):
next_step = steps.next_step(max_step)
_cleanup_common_directories_for_step(next_step, project_options)


def _cleanup_common_directories_for_step(step, project_options, parts=None):
if not parts:
parts = []

index = common.COMMAND_ORDER.index(step)

being_tried = False
if index <= common.COMMAND_ORDER.index('prime'):
if step <= steps.PRIME:
# Remove the priming area. Only remove the actual 'prime' directory if
# it's NOT being used in 'snap try'. We'll know that if it's
# bind-mounted somewhere.
Expand All @@ -129,16 +126,16 @@ def _cleanup_common_directories_for_step(step, project_options, parts=None):
"use by 'snap try'")
being_tried = True
_cleanup_common(
project_options.prime_dir, 'prime', message, parts,
project_options.prime_dir, steps.PRIME, message, parts,
remove_dir=remove_dir)

if index <= common.COMMAND_ORDER.index('stage'):
if step <= steps.STAGE:
# Remove the staging area.
_cleanup_common(
project_options.stage_dir, 'stage', 'Cleaning up staging area',
project_options.stage_dir, steps.STAGE, 'Cleaning up staging area',
parts)

if index <= common.COMMAND_ORDER.index('pull'):
if step <= steps.PULL:
# Remove the parts directory (but leave local plugins alone).
_cleanup_parts_dir(
project_options.parts_dir, project_options.local_plugins_dir,
Expand Down Expand Up @@ -178,8 +175,8 @@ def _cleanup_parts_dir(parts_dir, local_plugins_dir, parts):
except NotADirectoryError:
os.remove(path)
for part in parts:
part.mark_cleaned('build')
part.mark_cleaned('pull')
part.mark_cleaned(steps.BUILD)
part.mark_cleaned(steps.PULL)


def _cleanup_internal_snapcraft_dir():
Expand All @@ -191,15 +188,15 @@ def clean(project_options, parts, step=None):
# step defaults to None because that's how it comes from docopt when it's
# not set.
if not step:
step = 'pull'
step = steps.PULL

if not parts and step == 'pull':
if not parts and step == steps.PULL:
_cleanup_common_directories_for_step(step, project_options)
return

config = project_loader.load_config()

if not parts and (step == 'stage' or step == 'prime'):
if not parts and (step == steps.STAGE or step == steps.PRIME):
# If we've been asked to clean stage or prime without being given
# specific parts, just blow away those directories instead of
# doing it per part.
Expand All @@ -212,8 +209,8 @@ def clean(project_options, parts, step=None):
else:
parts = [part.name for part in config.all_parts]

staged_state = config.get_project_state('stage')
primed_state = config.get_project_state('prime')
staged_state = config.get_project_state(steps.STAGE)
primed_state = config.get_project_state(steps.PRIME)

_clean_parts(parts, step, config, staged_state, primed_state)

Expand Down
4 changes: 2 additions & 2 deletions snapcraft/internal/lifecycle/_packer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from progressbar import AnimatedMarker, ProgressBar

from snapcraft import file_utils
from snapcraft.internal import common, repo
from snapcraft.internal import common, repo, steps
from snapcraft.internal.indicators import is_dumb_terminal
from ._runner import execute

Expand All @@ -43,7 +43,7 @@ def _snap_data_from_dir(directory):
def snap(project_options, directory=None, output=None):
if not directory:
directory = project_options.prime_dir
execute('prime', project_options)
execute(steps.PRIME, project_options)

return pack(directory, output)

Expand Down
47 changes: 23 additions & 24 deletions snapcraft/internal/lifecycle/_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
project_loader,
repo,
states,
steps,
)
from snapcraft.internal.cache import SnapCache
from . import constants
Expand Down Expand Up @@ -160,15 +161,15 @@ def _init_run_states(self):
for part in self.config.all_parts:
steps_run[part.name] = set()
with config.CLIConfig() as cli_config:
for step in common.COMMAND_ORDER:
for step in steps.STEPS:
dirty_report = part.get_dirty_report(step)
if dirty_report:
self._handle_dirty(
part, step, dirty_report, cli_config)
elif not (part.should_step_run(step)):
steps_run[part.name].add(step)
part.notify_part_progress('Skipping {}'.format(step),
'(already ran)')
part.notify_part_progress(
'Skipping {}'.format(step.name), '(already ran)')

return steps_run

Expand All @@ -182,17 +183,15 @@ def run(self, step, part_names=None):
parts = self.config.all_parts
part_names = self.config.part_names

step_index = common.COMMAND_ORDER.index(step) + 1

for step in common.COMMAND_ORDER[0:step_index]:
if step == 'stage':
for current_step in steps.steps_required_for(step):
if current_step == steps.STAGE:
# XXX check only for collisions on the parts that have already
# been built --elopio - 20170713
pluginhandler.check_for_collisions(self.config.all_parts)
for part in parts:
if step not in self._steps_run[part.name]:
self._run_step(step, part, part_names)
self._steps_run[part.name].add(step)
if current_step not in self._steps_run[part.name]:
self._run_step(current_step, part, part_names)
self._steps_run[part.name].add(current_step)

self._create_meta(step, part_names)

Expand All @@ -201,11 +200,11 @@ def _run_step(self, step, part, part_names):
prereqs = self.parts_config.get_prereqs(part.name)

# Dependencies need to be primed to have paths to.
if step == 'prime':
required_step = 'prime'
if step == steps.PRIME:
required_step = steps.PRIME
# Or staged to be built with.
else:
required_step = 'stage'
required_step = steps.STAGE

step_prereqs = {p for p in prereqs
if required_step not in self._steps_run[p]}
Expand All @@ -216,22 +215,23 @@ def _run_step(self, step, part, part_names):
# a dependency.
logger.info(
'{!r} has prerequisites that need to be {}d: '
'{}'.format(part.name, required_step, ' '.join(step_prereqs)))
'{}'.format(
part.name, required_step.name, ' '.join(step_prereqs)))
self.run(required_step, step_prereqs)

# Run the preparation function for this step (if implemented)
with contextlib.suppress(AttributeError):
getattr(part, 'prepare_{}'.format(step))()
getattr(part, 'prepare_{}'.format(step.name))()

common.env = self.parts_config.build_env_for_part(part)
common.env.extend(self.config.project_env())

part = _replace_in_part(part)

getattr(part, step)()
getattr(part, step.name)()

def _create_meta(self, step, part_names):
if step == 'prime' and part_names == self.config.part_names:
if step == steps.PRIME and part_names == self.config.part_names:
common.env = self.config.snap_env()
meta.create_snap_packaging(
self.config.data, self.config.parts, self.project_options,
Expand All @@ -248,26 +248,25 @@ def _handle_dirty(self, part, step, dirty_report, cli_config):
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')
staged_state = self.config.get_project_state(steps.STAGE)
primed_state = self.config.get_project_state(steps.PRIME)

# We need to clean this step, but if it involves cleaning the stage
# step and it has dependents that have been built, the dependents need
# to first be cleaned (at least back to the build step). Do it
# automatically if configured to do so.
index = common.COMMAND_ORDER.index(step)
dependents = self.parts_config.get_dependents(part.name)
if (index <= common.COMMAND_ORDER.index('stage') and
not part.is_clean('stage') and dependents):
if (step <= steps.STAGE and not part.is_clean(steps.STAGE)
and dependents):
for dependent in self.config.all_parts:
if (dependent.name in dependents and
not dependent.is_clean('build')):
not dependent.is_clean(steps.BUILD)):
if dirty_action == config.OutdatedStepAction.ERROR:
raise errors.StepOutdatedError(
step=step, part=part.name, dependents=dependents)
else:
dependent.clean(
staged_state, primed_state, 'build',
staged_state, primed_state, steps.BUILD,
'(out of date)')

part.clean(staged_state, primed_state, step, '(out of date)')
4 changes: 3 additions & 1 deletion snapcraft/internal/lifecycle/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os

from snapcraft.internal import steps

SNAPCRAFT_INTERNAL_DIR = os.path.join('snap', '.snapcraft')
STEPS_TO_AUTOMATICALLY_CLEAN_IF_DIRTY = {'stage', 'prime'}
STEPS_TO_AUTOMATICALLY_CLEAN_IF_DIRTY = {steps.STAGE, steps.PRIME}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this look better if it were an attribute of the step?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, excellent idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.