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)')
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)')
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"), ))
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')))
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), )
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), )
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 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)
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))
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))
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')))
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"), ) )
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"), ) )
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
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"))