Example #1
0
    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()
Example #2
0
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}")
Example #3
0
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}")
Example #4
0
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
Example #5
0
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
Example #6
0
    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)
Example #7
0
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}")
Example #8
0
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}")
Example #9
0
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)
Example #10
0
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
Example #11
0
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
Example #12
0
def _validate_relative_paths(files):
    for d in files:
        if os.path.isabs(d):
            raise errors.PluginError('path "{}" must be relative'.format(d))