Example #1
0
    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))

        staged_state = self.config.get_project_state('stage')
        primed_state = self.config.get_project_state('prime')

        # We need to clean this step, but if it involves cleaning the stage
        # step and it has dependents that have been built, we need to ask for
        # them to first be cleaned (at least back to the build step).
        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):
            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))

        part.clean(staged_state, primed_state, step, '(out of date)')
Example #2
0
    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\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(
                "\nIn 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))

        staged_state = self.config.get_project_state('stage')
        primed_state = self.config.get_project_state('prime')

        # We need to clean this step, but if it involves cleaning the stage
        # step and it has dependents that have been built, we need to ask for
        # them to first be cleaned (at least back to the build step).
        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):
            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))

        part.clean(staged_state, primed_state, step, '(out of date)')
Example #3
0
def _adopt_info(config_data: Dict[str, Any],
                extracted_metadata: _metadata.ExtractedMetadata):
    ignored_keys = _adopt_keys(config_data, extracted_metadata)
    if ignored_keys:
        logger.warning(
            'The {keys} {plural_property} {plural_is} specified in adopted '
            'info as well as the YAML: taking the {plural_property} from the '
            'YAML'.format(
                keys=formatting_utils.humanize_list(list(ignored_keys), 'and'),
                plural_property=formatting_utils.pluralize(
                    ignored_keys, 'property', 'properties'),
                plural_is=formatting_utils.pluralize(ignored_keys, 'is',
                                                     'are')))
def _adopt_info(config_data: Dict[str, Any],
                extracted_metadata: _metadata.ExtractedMetadata):
    ignored_keys = _adopt_keys(config_data, extracted_metadata)
    if ignored_keys:
        logger.warning(
            "The {keys} {plural_property} {plural_is} specified in adopted "
            "info as well as the YAML: taking the {plural_property} from the "
            "YAML".format(
                keys=formatting_utils.humanize_list(list(ignored_keys), "and"),
                plural_property=formatting_utils.pluralize(
                    ignored_keys, "property", "properties"),
                plural_is=formatting_utils.pluralize(ignored_keys, "is",
                                                     "are"),
            ))
Example #5
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')))
Example #6
0
    def __init__(self,
                 *,
                 step,
                 part,
                 dirty_report=None,
                 outdated_report=None,
                 dependents=None):
        messages = []

        if dirty_report:
            messages.append(dirty_report.get_report())

        if outdated_report:
            messages.append(outdated_report.get_report())

        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.name, 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),
        )
Example #7
0
    def __init__(
        self, *, step, part, dirty_report=None, outdated_report=None, dependents=None
    ):
        messages = []

        if dirty_report:
            messages.append(dirty_report.get_report())

        if outdated_report:
            messages.append(outdated_report.get_report())

        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.name, 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),
        )
Example #8
0
def _adopt_info(
    config_data: Dict[str, Any], extracted_metadata: _metadata.ExtractedMetadata
):
    ignored_keys = _adopt_keys(config_data, extracted_metadata)
    if ignored_keys:
        logger.warning(
            "The {keys} {plural_property} {plural_is} specified in adopted "
            "info as well as the YAML: taking the {plural_property} from the "
            "YAML".format(
                keys=formatting_utils.humanize_list(list(ignored_keys), "and"),
                plural_property=formatting_utils.pluralize(
                    ignored_keys, "property", "properties"
                ),
                plural_is=formatting_utils.pluralize(ignored_keys, "is", "are"),
            )
        )
Example #9
0
    def get_report(self) -> str:
        """Get verbose report.

        :return: Report why the part is dirty.
        :rtype: str
        """
        messages = []  # type: List[str]

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

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

        if self.changed_dependencies:
            dependencies = [d.part_name for d in self.changed_dependencies]
            messages.append(
                "{} changed: {}\n".format(
                    formatting_utils.pluralize(
                        dependencies, "A dependency has", "Some dependencies have"
                    ),
                    formatting_utils.humanize_list(dependencies, "and"),
                )
            )

        return "".join(messages)
Example #10
0
 def __init__(self,
              *,
              step,
              part,
              dirty_properties=None,
              dirty_project_options=None,
              changed_dependencies=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 changed_dependencies:
         dependencies = [d['name'] for d in changed_dependencies]
         messages.append('{} changed: {}\n'.format(
             formatting_utils.pluralize(dependencies, 'A dependency has',
                                        'Some dependencies have'),
             formatting_utils.humanize_list(dependencies, 'and')))
     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.name, 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))
Example #11
0
 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))
Example #12
0
def _clean_parts(part_names, step, config, staged_state, primed_state):
    if not step:
        step = steps.next_step(None)

    for part_name in part_names:
        resulting_dirty_parts = _clean_part(part_name, step, config,
                                            staged_state, primed_state)

        parts_not_being_cleaned = resulting_dirty_parts.difference(part_names)
        if parts_not_being_cleaned:
            logger.warning(
                'Cleaned {!r}, which makes the following {} out of date: '
                '{}'.format(
                    part_name,
                    formatting_utils.pluralize(parts_not_being_cleaned, 'part',
                                               'parts'),
                    formatting_utils.humanize_list(parts_not_being_cleaned,
                                                   'and')))
Example #13
0
def _clean_parts(part_names, step, config, staged_state, primed_state):
    if not step:
        step = steps.next_step(None)

    for part_name in part_names:
        dirty_parts = _clean_part(part_name, step, config, staged_state, primed_state)
        dirty_part_names = {p.name for p in dirty_parts}

        parts_not_being_cleaned = dirty_part_names.difference(part_names)
        if parts_not_being_cleaned:
            logger.warning(
                "Cleaned {!r}, which makes the following {} out of date: "
                "{}".format(
                    part_name,
                    formatting_utils.pluralize(
                        parts_not_being_cleaned, "part", "parts"
                    ),
                    formatting_utils.humanize_list(parts_not_being_cleaned, "and"),
                )
            )
Example #14
0
def _clean_parts(part_names, step, config, staged_state, primed_state):
    if not step:
        step = steps.next_step(None)

    for part_name in part_names:
        dirty_parts = _clean_part(part_name, step, config, staged_state, primed_state)
        dirty_part_names = {p.name for p in dirty_parts}

        parts_not_being_cleaned = dirty_part_names.difference(part_names)
        if parts_not_being_cleaned:
            logger.warning(
                "Cleaned {!r}, which makes the following {} out of date: "
                "{}".format(
                    part_name,
                    formatting_utils.pluralize(
                        parts_not_being_cleaned, "part", "parts"
                    ),
                    formatting_utils.humanize_list(parts_not_being_cleaned, "and"),
                )
            )
Example #15
0
def _validate_architectures(instance):
    standalone_build_ons = collections.Counter()
    build_ons = collections.Counter()
    run_ons = collections.Counter()

    saw_strings = False
    saw_dicts = False

    for item in instance:
        # This could either be a dict or a string. In the latter case, the
        # schema will take care of it. We just need to further validate the
        # dict.
        if isinstance(item, str):
            saw_strings = True
        elif isinstance(item, dict):
            saw_dicts = True
            build_on = _get_architectures_set(item, "build-on")
            build_ons.update(build_on)

            # Add to the list of run-ons. However, if no run-on is specified,
            # we know it's implicitly the value of build-on, so use that
            # for validation instead.
            run_on = _get_architectures_set(item, "run-on")
            if run_on:
                run_ons.update(run_on)
            else:
                standalone_build_ons.update(build_on)

    # Architectures can either be a list of strings, or a list of objects.
    # Mixing the two forms is unsupported.
    if saw_strings and saw_dicts:
        raise jsonschema.exceptions.ValidationError(
            "every item must either be a string or an object",
            path=["architectures"],
            instance=instance,
        )

    # At this point, individual build-ons and run-ons have been validated,
    # we just need to validate them across each other.

    # First of all, if we have a `run-on: [all]` (or a standalone
    # `build-on: [all]`) then we should only have one item in the instance,
    # otherwise we know we'll have multiple snaps claiming they run on the same
    # architectures (i.e. all and something else).
    number_of_snaps = len(instance)
    if "all" in run_ons and number_of_snaps > 1:
        raise jsonschema.exceptions.ValidationError(
            "one of the items has 'all' in 'run-on', but there are {} "
            "items: upon release they will conflict. 'all' should only be "
            "used if there is a single item".format(number_of_snaps),
            path=["architectures"],
            instance=instance,
        )
    if "all" in build_ons and number_of_snaps > 1:
        raise jsonschema.exceptions.ValidationError(
            "one of the items has 'all' in 'build-on', but there are {} "
            "items: snapcraft doesn't know which one to use. 'all' should "
            "only be used if there is a single item".format(number_of_snaps),
            path=["architectures"],
            instance=instance,
        )

    # We want to ensure that multiple `run-on`s (or standalone `build-on`s)
    # don't include the same arch, or they'll clash with each other when
    # releasing.
    all_run_ons = run_ons + standalone_build_ons
    duplicates = {arch for (arch, count) in all_run_ons.items() if count > 1}
    if duplicates:
        raise jsonschema.exceptions.ValidationError(
            "multiple items will build snaps that claim to run on {}".format(
                formatting_utils.humanize_list(duplicates, "and")
            ),
            path=["architectures"],
            instance=instance,
        )

    # Finally, ensure that multiple `build-on`s don't include the same arch
    # or Snapcraft has no way of knowing which one to use.
    duplicates = {arch for (arch, count) in build_ons.items() if count > 1}
    if duplicates:
        raise jsonschema.exceptions.ValidationError(
            "{} {} present in the 'build-on' of multiple items, which means "
            "snapcraft doesn't know which 'run-on' to use when building on "
            "{} {}".format(
                formatting_utils.humanize_list(duplicates, "and"),
                formatting_utils.pluralize(duplicates, "is", "are"),
                formatting_utils.pluralize(duplicates, "that", "those"),
                formatting_utils.pluralize(duplicates, "architecture", "architectures"),
            ),
            path=["architectures"],
            instance=instance,
        )

    return True
Example #16
0
def inspect(*, provides: str, latest_step: bool, use_json: bool, **kwargs):
    """Inspect the current state of the project.

    By default, this command will print a tabulated summary of the current state of the
    project.
    """
    count = 0
    for option in (provides, latest_step):
        if option:
            count += 1

    if count > 1:
        raise errors.SnapcraftEnvironmentError(
            "Only one of --provides or --latest-step may be supplied")

    # Silence the plugin loader logger
    logging.getLogger(
        "snapcraft.internal.pluginhandler._plugin_loader").setLevel(
            logging.ERROR)
    project = get_project(**kwargs)
    config = project_loader.load_config(project)

    if provides:
        providing_parts = inspection.provides(provides, config.project,
                                              config.all_parts)
        providing_part_names = sorted([p.name for p in providing_parts])
        if use_json:
            print(
                json.dumps(
                    {
                        "path": provides,
                        "parts": providing_part_names
                    },
                    sort_keys=True,
                    indent=4,
                ))
        else:
            echo.info("This path was provided by the following {}:".format(
                formatting_utils.pluralize(providing_part_names, "part",
                                           "parts")))
            echo.info("\n".join(providing_part_names))

    elif latest_step:
        part, step, timestamp = inspection.latest_step(config.all_parts)
        directory = part.working_directory_for_step(step)
        if use_json:
            print(
                json.dumps(
                    {
                        "part": part.name,
                        "step": step.name,
                        "directory": directory,
                        "timestamp": timestamp,
                    },
                    sort_keys=True,
                    indent=4,
                ))
        else:
            echo.info(
                "The latest step that was run is the {!r} step of the {!r} part, "
                "which ran at {}. The working directory for this step is {!r}".
                format(
                    step.name,
                    part.name,
                    datetime.fromtimestamp(timestamp).strftime("%c"),
                    directory,
                ))

    else:
        summary = inspection.lifecycle_status(config)
        if use_json:
            summary_dict = dict()
            for part_summary in summary:
                part_name = part_summary.pop("part")
                summary_dict[part_name] = part_summary
            print(json.dumps(summary_dict, sort_keys=True, indent=4))
        else:
            print(tabulate(summary, headers="keys"))