def _load_code(self, plugin_name, properties, part_schema, definitions_schema): module_name = plugin_name.replace('-', '_') module = None with contextlib.suppress(ImportError): module = _load_local('x-{}'.format(plugin_name), self._project_options.local_plugins_dir) logger.info('Loaded local plugin for %s', plugin_name) if not module: with contextlib.suppress(ImportError): module = importlib.import_module( 'snapcraft.plugins.{}'.format(module_name)) if not module: logger.info('Searching for local plugin for %s', plugin_name) with contextlib.suppress(ImportError): module = _load_local(module_name, self._project_options.local_plugins_dir) if not module: raise errors.PluginError( 'unknown plugin: {!r}'.format(plugin_name)) plugin = _get_plugin(module) if not plugin: raise errors.PluginError( 'no plugin found in module {!r}'.format(plugin_name)) _validate_pull_and_build_properties( plugin_name, plugin, part_schema, definitions_schema) options = _make_options( part_schema, definitions_schema, properties, plugin.schema()) # For backwards compatibility we add the project to the plugin try: self.code = plugin(self.name, options, self._project_options) except TypeError: logger.warning( 'DEPRECATED: the plugin used by part {!r} needs to be updated ' 'to accept project options in its initializer. See ' 'https://github.com/snapcore/snapcraft/blob/master/docs/' 'plugins.md#initializing-a-plugin for more information'.format( self.name)) self.code = plugin(self.name, options) # This is for plugins that don't inherit from BasePlugin if not hasattr(self.code, 'project'): setattr(self.code, 'project', self._project_options) # This is for plugins that inherit from BasePlugin but don't have # project in init. if not self.code.project: self.code.project = self._project_options if self._project_options.is_cross_compiling: logger.debug( 'Setting {!r} as the compilation target for {!r}'.format( self._project_options.deb_arch, plugin_name)) self.code.enable_cross_compilation()
def get_plugin_for_base(plugin_name: str, *, build_base: str) -> PluginTypes: """ Return a suitable plugin implementation for build_base. :raises errors.PluginError: when a plugin implementation cannot be found for build_base. """ # TODO: segregate this more and remove the checks UnsupportedBase checks # from the plugins # Default for unknown, and core20 plugin_version = "v2" # Others use v1 if build_base == "core18": plugin_version = "v1" elif build_base == "core": raise errors.PluginBaseError(plugin_name=plugin_name, base=build_base) elif build_base == "": # This should never happen when using build_base from Project. raise RuntimeError("'build_base' cannot be unset.") try: plugin_classes = _PLUGINS[plugin_version] return plugin_classes[plugin_name] except KeyError: raise errors.PluginError(f"unknown plugin: {plugin_name!r}")
def _get_local_plugin_class(*, plugin_name: str, local_plugins_dir: str): with contextlib.suppress(ImportError): module = _load_local(plugin_name, local_plugins_dir) logger.info(f"Loaded local plugin for {plugin_name}") # v2 requires plugin implementation to be named "PluginImpl". if hasattr(module, "PluginImpl") and issubclass( module.PluginImpl, plugins.v2.PluginV2): return module.PluginImpl for attr in vars(module).values(): if not isinstance(attr, type): continue if not issubclass(attr, plugins.v1.PluginV1): continue if not hasattr(attr, "__module__"): continue logger.debug( f"Plugin attribute {attr!r} has __module__: {attr.__module__!r}" ) if attr.__module__.startswith("snapcraft.plugins"): continue return attr else: raise errors.PluginError(f"unknown plugin: {plugin_name!r}")
def load_plugin(plugin_name, part_name, project_options, properties, part_schema, definitions_schema): module_name = plugin_name.replace('-', '_') module = _load_module(module_name, plugin_name, project_options) plugin_class = _get_plugin(module) if not plugin_class: raise errors.PluginError( 'no plugin found in module {!r}'.format(plugin_name)) _validate_pull_and_build_properties(plugin_name, plugin_class, part_schema, definitions_schema) try: options = _make_options(part_schema, definitions_schema, properties, plugin_class.schema()) except jsonschema.ValidationError as e: error = YamlValidationError.from_validation_error(e) raise errors.PluginError('properties failed to load for {}: {}'.format( part_name, error.message)) # For backwards compatibility we add the project to the plugin try: plugin = plugin_class(part_name, options, project_options) except TypeError: logger.warning( 'DEPRECATED: the plugin used by part {!r} needs to be updated ' 'to accept project options in its initializer. See ' 'https://github.com/snapcore/snapcraft/blob/master/docs/' 'plugins.md#initializing-a-plugin for more information'.format( part_name)) plugin = plugin_class(part_name, options) # This is for plugins that don't inherit from BasePlugin if not hasattr(plugin, 'project'): setattr(plugin, 'project', project_options) # This is for plugins that inherit from BasePlugin but don't have # project in init. if not plugin.project: plugin.project = project_options if project_options.is_cross_compiling: logger.debug('Setting {!r} as the compilation target for {!r}'.format( project_options.deb_arch, plugin_name)) plugin.enable_cross_compilation() return plugin
def load_plugin( plugin_name, part_name, project_options, properties, part_schema, definitions_schema, local_plugins_dir: Optional[str] = None, ): if not local_plugins_dir: local_plugins_dir = os.path.join(os.getcwd(), "snap", "plugins") module_name = plugin_name.replace("-", "_") module = _load_module(module_name, plugin_name, local_plugins_dir) plugin_class = _get_plugin(module) if not plugin_class: raise errors.PluginError("no plugin found in module {!r}".format(plugin_name)) _validate_pull_and_build_properties( plugin_name, plugin_class, part_schema, definitions_schema ) try: options = _make_options( part_schema, definitions_schema, properties, plugin_class.schema() ) except jsonschema.ValidationError as e: error = YamlValidationError.from_validation_error(e) raise errors.PluginError( "properties failed to load for {}: {}".format(part_name, error.message) ) plugin = plugin_class(part_name, options, project_options) if project_options.is_cross_compiling: logger.debug( "Setting {!r} as the compilation target for {!r}".format( project_options.deb_arch, plugin_name ) ) plugin.enable_cross_compilation() return plugin
def __init__(self, *, plugin_name, part_name, part_properties, project_options, part_schema, definitions_schema): self.valid = False self.code = None self.config = {} self._name = part_name self._part_properties = _expand_part_properties( part_properties, part_schema) self.stage_packages = [] # Some legacy parts can have a '/' in them to separate the main project # part with the subparts. This is rather unfortunate as it affects the # the layout of parts inside the parts directory causing collisions # between the main project part and its subparts. part_name = part_name.replace('/', '\N{BIG SOLIDUS}') self._project_options = project_options self.deps = [] self.stagedir = project_options.stage_dir self.primedir = project_options.prime_dir parts_dir = project_options.parts_dir self.ubuntudir = os.path.join(parts_dir, part_name, 'ubuntu') self.statedir = os.path.join(parts_dir, part_name, 'state') self.sourcedir = os.path.join(parts_dir, part_name, 'src') # We don't need to set the source_handler on systems where we do not # build if sys.platform == 'linux': self.source_handler = self._get_source_handler( self._part_properties) else: self.source_handler = None self._build_attributes = BuildAttributes( self._part_properties['build-attributes']) self._migrate_state_file() try: self._load_code( plugin_name, self._part_properties, part_schema, definitions_schema) except jsonschema.ValidationError as e: error = errors.SnapcraftSchemaError.from_validation_error(e) raise errors.PluginError( 'properties failed to load for {}: {}'.format( part_name, error.message)) stage_packages = getattr(self.code, 'stage_packages', []) sources = getattr(self.code, 'PLUGIN_STAGE_SOURCES', None) self._stage_package_handler = StagePackageHandler( stage_packages, self.ubuntudir, sources=sources, project_options=self._project_options)
def get_plugin_for_base(plugin_name: str, *, build_base: str) -> PluginTypes: """ Return a suitable plugin implementation for build_base. :raises errors.PluginError: when a plugin implementation cannot be found for build_base. """ # TODO: segregate this more and remove the checks UnsupportedBase checks # from the plugins if build_base in ("core", "core16", "core18"): build_base = "legacy" try: plugin_classes = _PLUGINS[build_base] return plugin_classes[plugin_name] except KeyError: raise errors.PluginError(f"unknown plugin: {plugin_name!r}")
def _get_local_plugin_class(*, plugin_name: str, local_plugins_dir: str): with contextlib.suppress(ImportError): module_name = plugin_name.replace("-", "_") module = _load_local(f"{module_name}", local_plugins_dir) logger.info(f"Loaded local plugin for {plugin_name}") for attr in vars(module).values(): if not isinstance(attr, type): continue if not issubclass(attr, plugins.v1.PluginV1): continue if attr == plugins.v1.PluginV1: continue return attr else: raise errors.PluginError(f"unknown plugin: {plugin_name!r}")
def _make_options(part_name, part_schema, definitions_schema, properties, plugin_schema): # Make copies as these dictionaries are tampered with part_schema = part_schema.copy() properties = properties.copy() plugin_schema = _merged_part_and_plugin_schemas(part_schema, definitions_schema, plugin_schema) try: jsonschema.validate(properties, plugin_schema) except jsonschema.ValidationError as e: error = project_errors.YamlValidationError.from_validation_error(e) raise errors.PluginError("properties failed to load for {}: {}".format( part_name, error.message)) return _populate_options(properties, plugin_schema)
def _load_module(module_name, plugin_name, local_plugins_dir): module = None with contextlib.suppress(ImportError): module = _load_local("x-{}".format(plugin_name), local_plugins_dir) logger.info("Loaded local plugin for %s", plugin_name) if not module: with contextlib.suppress(ImportError): module = importlib.import_module("snapcraft.plugins.{}".format(module_name)) if not module: logger.info("Searching for local plugin for %s", plugin_name) with contextlib.suppress(ImportError): module = _load_local(module_name, local_plugins_dir) if not module: raise errors.PluginError("unknown plugin: {!r}".format(plugin_name)) return module
def _load_module(module_name, plugin_name, project_options): module = None with contextlib.suppress(ImportError): module = _load_local('x-{}'.format(plugin_name), project_options.local_plugins_dir) logger.info('Loaded local plugin for %s', plugin_name) if not module: with contextlib.suppress(ImportError): module = importlib.import_module( 'snapcraft.plugins.{}'.format(module_name)) if not module: logger.info('Searching for local plugin for %s', plugin_name) with contextlib.suppress(ImportError): module = _load_local(module_name, project_options.local_plugins_dir) if not module: raise errors.PluginError( 'unknown plugin: {!r}'.format(plugin_name)) return module
def _validate_relative_paths(files): for d in files: if os.path.isabs(d): raise errors.PluginError('path "{}" must be relative'.format(d))