Skip to content

Commit c2d6012

Browse files
Chris PattersonSergio Schvezov
authored andcommitted
plugin handler: load legacy plugins prefixed with 'x-'
Required for compatibility. Signed-off-by: Chris Patterson <chris.patterson@canonical.com>
1 parent 58c66b7 commit c2d6012

5 files changed

Lines changed: 162 additions & 9 deletions

File tree

snapcraft/internal/pluginhandler/_plugin_loader.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import importlib
1919
import logging
2020
import sys
21+
from pathlib import Path
2122

2223
import jsonschema
2324

@@ -74,21 +75,48 @@ def load_plugin(
7475
return plugin
7576

7677

77-
def _load_local(module_name: str, local_plugin_dir: str):
78-
sys.path = [local_plugin_dir] + sys.path
79-
logger.debug(f"Loading plugin module {module_name!r} with sys.path {sys.path!r}")
80-
try:
81-
module = importlib.import_module(module_name)
82-
finally:
83-
sys.path.pop(0)
78+
def _load_compat_x_prefix(plugin_name: str, module_name: str, local_plugin_dir: str):
79+
compat_path = Path(local_plugin_dir, f"x-{plugin_name}.py")
80+
if not compat_path.exists():
81+
return None
82+
83+
preferred_name = f"{module_name}.py"
84+
logger.warning(
85+
f"Legacy plugin name detected, please rename the plugin's file name {compat_path.name!r} to {preferred_name!r}."
86+
)
87+
88+
spec = importlib.util.spec_from_file_location(plugin_name, compat_path)
89+
if spec.loader is None:
90+
return None
91+
92+
# Prevent mypy type complaints by asserting type.
93+
assert isinstance(spec.loader, importlib.abc.Loader)
94+
95+
module = importlib.util.module_from_spec(spec)
96+
spec.loader.exec_module(module)
97+
return module
98+
99+
100+
def _load_local(plugin_name: str, local_plugin_dir: str):
101+
module_name = plugin_name.replace("-", "_")
102+
103+
module = _load_compat_x_prefix(plugin_name, module_name, local_plugin_dir)
104+
if module is None:
105+
sys.path = [local_plugin_dir] + sys.path
106+
logger.debug(
107+
f"Loading plugin module {module_name!r} with sys.path {sys.path!r}"
108+
)
109+
try:
110+
module = importlib.import_module(module_name)
111+
finally:
112+
sys.path.pop(0)
84113

85114
return module
86115

87116

88117
def _get_local_plugin_class(*, plugin_name: str, local_plugins_dir: str):
89118
with contextlib.suppress(ImportError):
90-
module_name = plugin_name.replace("-", "_")
91-
module = _load_local(f"{module_name}", local_plugins_dir)
119+
module = _load_local(plugin_name, local_plugins_dir)
92120
logger.info(f"Loaded local plugin for {plugin_name}")
93121

94122
for attr in vars(module).values():
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
!PullState
2+
assets:
3+
build-packages: !!set {}
4+
build-snaps: !!set {}
5+
source-details: null
6+
stage-packages: []
7+
extracted_metadata:
8+
files: []
9+
metadata: !ExtractedMetadata
10+
_data: {}
11+
common_id_list: []
12+
project_options:
13+
deb_arch: @EXPECTED_ARCHITECTURE@
14+
properties:
15+
foo: null
16+
override-pull: snapcraftctl pull
17+
parse-info: []
18+
plugin: local-plugin
19+
source: .
20+
source-branch: ''
21+
source-commit: ''
22+
source-depth: 0
23+
source-subdir: ''
24+
source-tag: ''
25+
source-type: ''
26+
stage-packages: []
27+
schema_properties:
28+
- foo
29+
- stage-packages
30+
scriptlet_metadata: !ExtractedMetadata
31+
_data: {}
32+
common_id_list: []
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
summary: Use local plugins to build a snap
2+
3+
environment:
4+
SNAP_DIR/xcompat1: ../snaps/x-compat-name
5+
6+
prepare: |
7+
#shellcheck source=tests/spread/tools/snapcraft-yaml.sh
8+
. "$TOOLS_DIR/snapcraft-yaml.sh"
9+
set_base "$SNAP_DIR/snap/snapcraft.yaml"
10+
11+
deb_arch=$(dpkg --print-architecture)
12+
sed -i state-pull -e"s/\(.*deb_arch:\) @EXPECTED_ARCHITECTURE@$/\1 $deb_arch/"
13+
14+
restore: |
15+
expected_pull_state="$(pwd)/state-pull"
16+
17+
cd "$SNAP_DIR"
18+
snapcraft clean
19+
rm -f ./*.snap
20+
21+
#shellcheck source=tests/spread/tools/snapcraft-yaml.sh
22+
. "$TOOLS_DIR/snapcraft-yaml.sh"
23+
restore_yaml "snap/snapcraft.yaml"
24+
25+
git checkout "${expected_pull_state}"
26+
27+
execute: |
28+
expected_pull_state="$(pwd)/state-pull"
29+
30+
cd "$SNAP_DIR"
31+
32+
snapcraft build
33+
34+
if [ ! -f parts/x-local-plugin/install/build-stamp ]; then
35+
echo "Local plugin failed to create 'build-stamp"
36+
exit 1
37+
fi
38+
39+
if ! diff -U10 parts/x-local-plugin/state/pull "$expected_pull_state"; then
40+
echo "Pull state does not match"
41+
exit 1
42+
fi
43+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2+
#
3+
# Copyright (C) 2015 Canonical Ltd
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License version 3 as
7+
# published by the Free Software Foundation.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
from snapcraft import BasePlugin
18+
19+
20+
class LocalPlugin(BasePlugin):
21+
@classmethod
22+
def schema(cls):
23+
schema = super().schema()
24+
25+
schema["properties"]["foo"] = {"type": "string"}
26+
27+
return schema
28+
29+
@classmethod
30+
def get_pull_properties(cls):
31+
return ["foo", "stage-packages"]
32+
33+
@classmethod
34+
def get_build_properties(cls):
35+
return ["foo", "stage-packages"]
36+
37+
def build(self):
38+
return self.run(["touch", "build-stamp"], self.installdir)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: test-local-plugins
2+
base: core18
3+
version: "0.1"
4+
summary: local plugin in snap/plugins
5+
description: Tests if local plugins load and can build
6+
confinement: strict
7+
grade: devel
8+
9+
parts:
10+
x-local-plugin:
11+
plugin: local-plugin
12+
source: .

0 commit comments

Comments
 (0)