Exemple #1
0
    def _rerun_step(self, *, step: steps.Step, part, progress, hint='',
                    prerequisite_step: steps.Step=steps.STAGE):
        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 any steps
        # upon which other parts depend, those parts need to be marked as dirty
        # so they can run again, taking advantage of the new dependency.
        dirty_parts = mark_dependents_dirty(part.name, step, self.config)

        # First clean the step, then run it again
        part.clean(staged_state, primed_state, step)

        for current_step in [step] + step.next_steps():
            self._dirty_reports[part.name][current_step] = None
            self._steps_run[part.name].discard(current_step)

        self._run_step(
            step=step, part=part, progress=progress, hint=hint,
            prerequisite_step=prerequisite_step)

        if dirty_parts:
            logger.warning(
                'The following {} now out of date: {}'.format(
                    formatting_utils.pluralize(
                        dirty_parts, 'part is', 'parts are'),
                    formatting_utils.humanize_list(dirty_parts, 'and')))
Exemple #2
0
    def should_step_run(self, part: pluginhandler.PluginHandler,
                        step: steps.Step) -> bool:
        """Determine if a given step of a given part should run.

        :param pluginhandler.PluginHandler part: Part in question.
        :param steps.Step step: Step in question.
        :return: Whether or not step should run.
        :rtype: bool

        A given step should run if it:
            1. Hasn't yet run
            2. Is dirty
            3. Is outdated
            4. Either (1), (2), or (3) apply to any earlier steps in the part's
               lifecycle
        """
        if (not self.has_step_run(part, step)
                or self.get_outdated_report(part, step) is not None
                or self.get_dirty_report(part, step) is not None):
            return True

        previous_step = step.previous_step()
        if previous_step:
            return self.should_step_run(part, previous_step)

        return False
Exemple #3
0
    def get_outdated_report(self, step: steps.Step) -> OutdatedReport:
        """Return an OutdatedReport class describing why the step is outdated.

        A step is considered to be outdated if an earlier step in the lifecycle
        has been run more recently, or if the source code changed on disk.
        This means the step needs to be updated by taking modified files from
        the previous step. This is in contrast to a "dirty" step, which must
        be cleaned and run again.

        :param steps.Step step: The step to be checked.
        :returns: OutdatedReport if the step is outdated, None otherwise.
        """

        try:
            return getattr(self, "check_{}".format(step.name))()
        except AttributeError:
            with contextlib.suppress(errors.StepHasNotRunError):
                timestamp = self.step_timestamp(step)

                for previous_step in reversed(step.previous_steps()):
                    # Has a previous step run since this one ran? Then this
                    # step needs to be updated.
                    with contextlib.suppress(errors.StepHasNotRunError):
                        if timestamp < self.step_timestamp(previous_step):
                            return OutdatedReport(previous_step_modified=previous_step)
            return None
Exemple #4
0
    def should_step_run(
        self, part: pluginhandler.PluginHandler, step: steps.Step
    ) -> bool:
        """Determine if a given step of a given part should run.

        :param pluginhandler.PluginHandler part: Part in question.
        :param steps.Step step: Step in question.
        :return: Whether or not step should run.
        :rtype: bool

        A given step should run if it:
            1. Hasn't yet run
            2. Is dirty
            3. Is outdated
            4. Either (1), (2), or (3) apply to any earlier steps in the part's
               lifecycle
        """
        if (
            not self.has_step_run(part, step)
            or self.get_outdated_report(part, step) is not None
            or self.get_dirty_report(part, step) is not None
        ):
            return True

        previous_step = step.previous_step()
        if previous_step:
            return self.should_step_run(part, previous_step)

        return False
Exemple #5
0
    def get_outdated_report(self, step: steps.Step) -> OutdatedReport:
        """Return an OutdatedReport class describing why the step is outdated.

        A step is considered to be outdated if an earlier step in the lifecycle
        has been run more recently, or if the source code changed on disk.
        This means the step needs to be updated by taking modified files from
        the previous step. This is in contrast to a "dirty" step, which must
        be cleaned and run again.

        :param steps.Step step: The step to be checked.
        :returns: OutdatedReport if the step is outdated, None otherwise.
        """

        try:
            return getattr(self, "check_{}".format(step.name))()
        except AttributeError:
            with contextlib.suppress(errors.StepHasNotRunError):
                timestamp = self.step_timestamp(step)

                for previous_step in reversed(step.previous_steps()):
                    # Has a previous step run since this one ran? Then this
                    # step needs to be updated.
                    with contextlib.suppress(errors.StepHasNotRunError):
                        if timestamp < self.step_timestamp(previous_step):
                            return OutdatedReport(previous_step_modified=previous_step)
            return None
Exemple #6
0
def _execute(  # noqa: C901
        step: steps.Step,
        parts: str,
        pack_project: bool = False,
        output: str = None,
        shell: bool = False,
        shell_after: bool = False,
        destructive_mode: bool = False,
        **kwargs) -> "Project":
    # fmt: on
    provider = "host" if destructive_mode else None
    build_environment = env.BuilderEnvironmentConfig(force_provider=provider)
    project = get_project(is_managed_host=build_environment.is_managed_host,
                          **kwargs)

    conduct_project_sanity_check(project)

    if build_environment.is_managed_host or build_environment.is_host:
        project_config = project_loader.load_config(project)
        lifecycle.execute(step, project_config, parts)
        if pack_project:
            _pack(project.prime_dir, output=output)
    else:
        build_provider_class = build_providers.get_provider_for(
            build_environment.provider)
        echo.info("Launching a VM.")
        with build_provider_class(project=project, echoer=echo) as instance:
            instance.mount_project()
            try:
                if shell:
                    # shell means we want to do everything right up to the previous
                    # step and then go into a shell instead of the requested step.
                    # the "snap" target is a special snowflake that has not made its
                    # way to be a proper step.
                    previous_step = None
                    if pack_project:
                        previous_step = steps.PRIME
                    elif step > steps.PULL:
                        previous_step = step.previous_step()
                    # steps.PULL is the first step, so we would directly shell into it.
                    if previous_step:
                        instance.execute_step(previous_step)
                elif pack_project:
                    instance.pack_project(output=output)
                else:
                    instance.execute_step(step)
            except Exception:
                if project.debug:
                    instance.shell()
                else:
                    echo.warning(
                        "Run the same command again with --debug to shell into the environment "
                        "if you wish to introspect this failure.")
                    raise
            else:
                if shell or shell_after:
                    instance.shell()
    return project
Exemple #7
0
    def _rerun_step(self, *, step: steps.Step, part, progress, hint=""):
        staged_state = self.config.get_project_state(steps.STAGE)
        primed_state = self.config.get_project_state(steps.PRIME)

        # First clean the step, then run it again
        part.clean(staged_state, primed_state, step)

        # Uncache this and later steps since we just cleaned them: their status
        # has changed
        for current_step in [step] + step.next_steps():
            self._cache.clear_step(part, current_step)

        self._run_step(step=step, part=part, progress=progress, hint=hint)
Exemple #8
0
    def _rerun_step(self, *, step: steps.Step, part, progress, hint=""):
        staged_state = self.config.get_project_state(steps.STAGE)
        primed_state = self.config.get_project_state(steps.PRIME)

        # First clean the step, then run it again
        part.clean(staged_state, primed_state, step)

        # Uncache this and later steps since we just cleaned them: their status
        # has changed
        for current_step in [step] + step.next_steps():
            self._cache.clear_step(part, current_step)

        self._run_step(step=step, part=part, progress=progress, hint=hint)
Exemple #9
0
    def run(self, step: steps.Step, part_names=None):
        if part_names:
            self.parts_config.validate(part_names)
            # self.config.all_parts is already ordered, let's not lose that
            # and keep using a list.
            parts = [p for p in self.config.all_parts if p.name in part_names]
            processed_part_names = part_names
        else:
            parts = self.config.all_parts
            processed_part_names = self.config.part_names

        with config.CLIConfig() as cli_config:
            for current_step in step.previous_steps() + [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 self._dirty_reports[part.name][current_step]:
                        self._handle_dirty(
                            part, current_step,
                            self._dirty_reports[part.name][current_step],
                            cli_config)
                    elif current_step in self._steps_run[part.name]:
                        # By default, if a step has already run, don't run it
                        # again. However, automatically clean and re-run the
                        # step if all the following conditions apply:
                        #
                        #   1. The step is the exact step that was requested
                        #      (not an earlier one)
                        #   2. The part was explicitly specified
                        if (part_names and current_step == step and
                                part.name in part_names):
                            getattr(self, '_re{}'.format(
                                current_step.name))(part)
                        else:
                            notify_part_progress(
                                part,
                                'Skipping {}'.format(current_step.name),
                                '(already ran)')
                    else:
                        getattr(self, '_run_{}'.format(
                            current_step.name))(part)
                        self._steps_run[part.name].add(current_step)

        self._create_meta(step, processed_part_names)
Exemple #10
0
    def run(self, step: steps.Step, part_names=None):
        if part_names:
            self.parts_config.validate(part_names)
            # self.config.all_parts is already ordered, let's not lose that
            # and keep using a list.
            parts = [p for p in self.config.all_parts if p.name in part_names]
            processed_part_names = part_names
        else:
            parts = self.config.all_parts
            processed_part_names = self.config.part_names

        with config.CLIConfig() as cli_config:
            for current_step in step.previous_steps() + [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:
                    self._handle_step(part_names, part, step, current_step, cli_config)

        self._create_meta(step, processed_part_names)
Exemple #11
0
    def run(self, step: steps.Step, part_names=None):
        if part_names:
            self.parts_config.validate(part_names)
            # self.config.all_parts is already ordered, let's not lose that
            # and keep using a list.
            parts = [p for p in self.config.all_parts if p.name in part_names]
            processed_part_names = part_names
        else:
            parts = self.config.all_parts
            processed_part_names = self.config.part_names

        with config.CLIConfig() as cli_config:
            for current_step in step.previous_steps() + [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:
                    self._handle_step(part_names, part, step, current_step, cli_config)

        self._create_meta(step, processed_part_names)
Exemple #12
0
def _execute(  # noqa: C901
        step: steps.Step,
        parts: str,
        pack_project: bool = False,
        output: str = None,
        shell: bool = False,
        shell_after: bool = False,
        destructive_mode: bool = False,
        **kwargs) -> "Project":
    _clean_provider_error()
    provider = "host" if destructive_mode else None
    build_environment = env.BuilderEnvironmentConfig(force_provider=provider)
    project = get_project(is_managed_host=build_environment.is_managed_host,
                          **kwargs)

    echo.wrapped("Using {!r}: Project assets will be "
                 "searched for from the {!r} directory.".format(
                     project.info.snapcraft_yaml_file_path,
                     os.path.relpath(project._get_snapcraft_assets_dir(),
                                     project._project_dir),
                 ))

    conduct_project_sanity_check(project)

    if build_environment.is_managed_host or build_environment.is_host:
        project_config = project_loader.load_config(project)
        lifecycle.execute(step, project_config, parts)
        if pack_project:
            _pack(project.prime_dir, output=output)
    else:
        build_provider_class = build_providers.get_provider_for(
            build_environment.provider)
        try:
            build_provider_class.ensure_provider()
        except build_providers.errors.ProviderNotFound as provider_error:
            if provider_error.prompt_installable:
                click.echo(str(provider_error))
                if click.confirm("Would you like to install it now?"):
                    build_provider_class.setup_provider(echoer=echo)
                else:
                    raise provider_error
            else:
                raise provider_error

        echo.info("Launching a VM.")
        with build_provider_class(project=project, echoer=echo) as instance:
            instance.mount_project()
            try:
                if shell:
                    # shell means we want to do everything right up to the previous
                    # step and then go into a shell instead of the requested step.
                    # the "snap" target is a special snowflake that has not made its
                    # way to be a proper step.
                    previous_step = None
                    if pack_project:
                        previous_step = steps.PRIME
                    elif step > steps.PULL:
                        previous_step = step.previous_step()
                    # steps.PULL is the first step, so we would directly shell into it.
                    if previous_step:
                        instance.execute_step(previous_step)
                elif pack_project:
                    instance.pack_project(output=output)
                else:
                    instance.execute_step(step)
            except Exception:
                _retrieve_provider_error(instance)
                if project.debug:
                    instance.shell()
                else:
                    echo.warning(
                        "Run the same command again with --debug to shell into the environment "
                        "if you wish to introspect this failure.")
                    raise
            else:
                if shell or shell_after:
                    instance.shell()
    return project
Exemple #13
0
def _execute(  # noqa: C901
    step: steps.Step,
    parts: str,
    pack_project: bool = False,
    output: Optional[str] = None,
    shell: bool = False,
    shell_after: bool = False,
    setup_prime_try: bool = False,
    **kwargs,
) -> "Project":
    # Cleanup any previous errors.
    _clean_provider_error()

    build_provider = get_build_provider(**kwargs)
    build_provider_flags = get_build_provider_flags(build_provider, **kwargs)
    apply_host_provider_flags(build_provider_flags)

    is_managed_host = build_provider == "managed-host"

    # Temporary fix to ignore target_arch.
    if kwargs.get("target_arch") is not None and build_provider in ["multipass", "lxd"]:
        echo.warning(
            "Ignoring '--target-arch' flag.  This flag requires --destructive-mode and is unsupported with Multipass and LXD build providers."
        )
        kwargs.pop("target_arch")

    project = get_project(is_managed_host=is_managed_host, **kwargs)
    conduct_project_sanity_check(project, **kwargs)

    project_path = pathlib.Path(project._project_dir)
    if project_path.name in ["build-aux", "snap"]:
        echo.warning(
            f"Snapcraft is running in directory {project_path.name!r}.  If this is the snap assets directory, please run snapcraft from {project_path.parent}."
        )

    if build_provider in ["host", "managed-host"]:
        project_config = project_loader.load_config(project)
        lifecycle.execute(step, project_config, parts)
        if pack_project:
            _pack(
                project.prime_dir,
                compression=project._snap_meta.compression,
                output=output,
            )
    else:
        build_provider_class = build_providers.get_provider_for(build_provider)
        try:
            build_provider_class.ensure_provider()
        except build_providers.errors.ProviderNotFound as provider_error:
            if provider_error.prompt_installable:
                if echo.is_tty_connected() and echo.confirm(
                    "Support for {!r} needs to be set up. "
                    "Would you like to do it now?".format(provider_error.provider)
                ):
                    build_provider_class.setup_provider(echoer=echo)
                else:
                    raise provider_error
            else:
                raise provider_error

        with build_provider_class(
            project=project, echoer=echo, build_provider_flags=build_provider_flags
        ) as instance:
            try:
                if shell:
                    # shell means we want to do everything right up to the previous
                    # step and then go into a shell instead of the requested step.
                    # the "snap" target is a special snowflake that has not made its
                    # way to be a proper step.
                    previous_step = None
                    if pack_project:
                        previous_step = steps.PRIME
                    elif step > steps.PULL:
                        previous_step = step.previous_step()
                    # steps.PULL is the first step, so we would directly shell into it.
                    if previous_step:
                        instance.execute_step(previous_step)
                elif pack_project:
                    instance.pack_project(output=output)
                elif setup_prime_try:
                    instance.expose_prime()
                    instance.execute_step(step)
                else:
                    instance.execute_step(step)
            except Exception:
                _retrieve_provider_error(instance)
                if project.debug:
                    instance.shell()
                else:
                    echo.warning(
                        "Run the same command again with --debug to shell into the environment "
                        "if you wish to introspect this failure."
                    )
                    raise
            else:
                if shell or shell_after:
                    instance.shell()
    return project
Exemple #14
0
def _execute(  # noqa: C901
    step: steps.Step,
    parts: str,
    pack_project: bool = False,
    output: Optional[str] = None,
    shell: bool = False,
    shell_after: bool = False,
    setup_prime_try: bool = False,
    **kwargs,
) -> "Project":
    # Cleanup any previous errors.
    _clean_provider_error()

    build_provider = get_build_provider(**kwargs)
    build_provider_flags = get_build_provider_flags(build_provider, **kwargs)
    apply_host_provider_flags(build_provider_flags)

    is_managed_host = build_provider == "managed-host"

    project = get_project(is_managed_host=is_managed_host, **kwargs)
    conduct_project_sanity_check(project, **kwargs)

    if build_provider in ["host", "managed-host"]:
        project_config = project_loader.load_config(project)
        lifecycle.execute(step, project_config, parts)
        if pack_project:
            _pack(project.prime_dir, output=output)
    else:
        build_provider_class = build_providers.get_provider_for(build_provider)
        try:
            build_provider_class.ensure_provider()
        except build_providers.errors.ProviderNotFound as provider_error:
            if provider_error.prompt_installable:
                if echo.is_tty_connected() and echo.confirm(
                        "Support for {!r} needs to be set up. "
                        "Would you like to do that it now?".format(
                            provider_error.provider)):
                    build_provider_class.setup_provider(echoer=echo)
                else:
                    raise provider_error
            else:
                raise provider_error

        with build_provider_class(
                project=project,
                echoer=echo,
                build_provider_flags=build_provider_flags) as instance:
            instance.mount_project()
            try:
                if shell:
                    # shell means we want to do everything right up to the previous
                    # step and then go into a shell instead of the requested step.
                    # the "snap" target is a special snowflake that has not made its
                    # way to be a proper step.
                    previous_step = None
                    if pack_project:
                        previous_step = steps.PRIME
                    elif step > steps.PULL:
                        previous_step = step.previous_step()
                    # steps.PULL is the first step, so we would directly shell into it.
                    if previous_step:
                        instance.execute_step(previous_step)
                elif pack_project:
                    instance.pack_project(output=output)
                elif setup_prime_try:
                    instance.expose_prime()
                    instance.execute_step(step)
                else:
                    instance.execute_step(step)
            except Exception:
                _retrieve_provider_error(instance)
                if project.debug:
                    instance.shell()
                else:
                    echo.warning(
                        "Run the same command again with --debug to shell into the environment "
                        "if you wish to introspect this failure.")
                    raise
            else:
                if shell or shell_after:
                    instance.shell()
    return project
Exemple #15
0
def _execute(  # noqa: C901
    step: steps.Step,
    parts: str,
    pack_project: bool = False,
    output: str = None,
    shell: bool = False,
    shell_after: bool = False,
    **kwargs
) -> "Project":
    # fmt: on
    if sys.platform == "darwin":
        default_provider = "multipass"
    else:
        default_provider = "host"

    build_environment = env.BuilderEnvironmentConfig(default=default_provider)
    project = get_project(is_managed_host=build_environment.is_managed_host, **kwargs)

    conduct_project_sanity_check(project)

    #  When we are ready to pull the trigger we will trigger this when
    # project.info.base is set
    if build_environment.is_multipass:
        build_provider_class = build_providers.get_provider_for(
            build_environment.provider
        )
        echo.info("Launching a VM.")
        with build_provider_class(project=project, echoer=echo) as instance:
            instance.mount_project()
            try:
                if shell:
                    # shell means we want to do everything right up to the previous
                    # step and then go into a shell instead of the requested step.
                    # the "snap" target is a special snowflake that has not made its
                    # way to be a proper step.
                    previous_step = None
                    if pack_project:
                        previous_step = steps.PRIME
                    elif step > steps.PULL:
                        previous_step = step.previous_step()
                    # steps.PULL is the first step, so we would directly shell into it.
                    if previous_step:
                        instance.execute_step(previous_step)
                elif pack_project:
                    instance.pack_project(output=output)
                else:
                    instance.execute_step(step)
            except Exception:
                if project.debug:
                    instance.shell()
                else:
                    echo.warning("Run the same command again with --debug to shell into the environment "
                                 "if you wish to introspect this failure.")
                    raise
            else:
                if shell or shell_after:
                    instance.shell()
    elif build_environment.is_managed_host or build_environment.is_host:
        project_config = project_loader.load_config(project)
        lifecycle.execute(step, project_config, parts)
        if pack_project:
            _pack(project.prime_dir, output=output)
    else:
        # containerbuild takes a snapcraft command name, not a step
        lifecycle.containerbuild(command=step.name, project=project, args=parts)
        if pack_project:
            _pack(project.prime_dir, output=output)
    return project
Exemple #16
0
def _execute(  # noqa: C901
        step: steps.Step,
        parts: str,
        pack_project: bool = False,
        output: str = None,
        shell: bool = False,
        shell_after: bool = False,
        destructive_mode: bool = False,
        **kwargs) -> "Project":
    # fmt: on
    _clean_provider_error()
    provider = "host" if destructive_mode else None
    build_environment = env.BuilderEnvironmentConfig(force_provider=provider)
    try:
        conduct_build_environment_sanity_check(build_environment.provider)
    except MultipassMissingInstallableError as e:
        click.echo("You need multipass installed to build snaps "
                   "(https://github.com/CanonicalLtd/multipass).")
        if click.confirm("Would you like to install it now?"):
            _install_multipass()
        else:
            raise errors.SnapcraftEnvironmentError(
                "multipass is required to continue.") from e

    project = get_project(is_managed_host=build_environment.is_managed_host,
                          **kwargs)

    conduct_project_sanity_check(project)

    if build_environment.is_managed_host or build_environment.is_host:
        project_config = project_loader.load_config(project)
        lifecycle.execute(step, project_config, parts)
        if pack_project:
            _pack(project.prime_dir, output=output)
    else:
        build_provider_class = build_providers.get_provider_for(
            build_environment.provider)
        echo.info("Launching a VM.")
        with build_provider_class(project=project, echoer=echo) as instance:
            instance.mount_project()
            try:
                if shell:
                    # shell means we want to do everything right up to the previous
                    # step and then go into a shell instead of the requested step.
                    # the "snap" target is a special snowflake that has not made its
                    # way to be a proper step.
                    previous_step = None
                    if pack_project:
                        previous_step = steps.PRIME
                    elif step > steps.PULL:
                        previous_step = step.previous_step()
                    # steps.PULL is the first step, so we would directly shell into it.
                    if previous_step:
                        instance.execute_step(previous_step)
                elif pack_project:
                    instance.pack_project(output=output)
                else:
                    instance.execute_step(step)
            except Exception:
                _retrieve_provider_error(instance)
                if project.debug:
                    instance.shell()
                else:
                    echo.warning(
                        "Run the same command again with --debug to shell into the environment "
                        "if you wish to introspect this failure.")
                    raise
            else:
                if shell or shell_after:
                    instance.shell()
    return project