Skip to content

Commit 8994b4b

Browse files
authored
Add is_build_plugin plugin property to indicate additional requirements for build-plugins (#117)
* Add a `has_fixed_supported_configs` plugin property Add a `has_fixed_supported_configs` that indicates whether `get_supported_configs()` always returns a fixed list that does not depend on the system state. Only plugins featuring this flag can be used with `plugin-use = "build"`. Signed-off-by: Michał Górny <mgorny@quansight.com> * Rename to `is_build_plugin` Signed-off-by: Michał Górny <mgorny@quansight.com> --------- Signed-off-by: Michał Górny <mgorny@quansight.com>
1 parent 2c0e6eb commit 8994b4b

6 files changed

Lines changed: 84 additions & 5 deletions

File tree

tests/mocked_plugins.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class MockedEntryPoint:
1818
class MockedPluginA(PluginType):
1919
namespace = "test_namespace" # pyright: ignore[reportAssignmentType,reportIncompatibleMethodOverride]
2020

21+
is_build_plugin = True
22+
2123
def get_all_configs(self) -> list[VariantFeatureConfigType]:
2224
return [
2325
VariantFeatureConfig("name1", ["val1a", "val1b", "val1c", "val1d"]),

tests/test_api.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from variantlib.constants import VARIANTS_JSON_SCHEMA_URL
5050
from variantlib.constants import VARIANTS_JSON_VARIANT_DATA_KEY
5151
from variantlib.constants import VariantsJsonDict
52+
from variantlib.errors import PluginError
5253
from variantlib.errors import ValidationError
5354
from variantlib.models import provider as pconfig
5455
from variantlib.models import variant as vconfig
@@ -764,3 +765,32 @@ def test_make_variant_dist_info_invalid_build_plugin() -> None:
764765
variant_info=vinfo,
765766
expand_build_plugin_properties=True,
766767
)
768+
769+
770+
def test_make_variant_dist_info_really_invalid_build_plugin() -> None:
771+
vdesc = VariantDescription(
772+
[
773+
VariantProperty("second_namespace", "name3", "val3a"),
774+
]
775+
)
776+
plugin_api = "tests.mocked_plugins:MockedPluginB"
777+
vinfo = VariantInfo(
778+
namespace_priorities=["second_namespace"],
779+
providers={
780+
"second_namespace": ProviderInfo(
781+
plugin_api=plugin_api,
782+
plugin_use=PluginUse.BUILD,
783+
)
784+
},
785+
)
786+
787+
with pytest.raises(
788+
PluginError,
789+
match=r"Providers for namespaces {'second_namespace'} do not provide fixed "
790+
r"supported configs, they cannot be used with plugin-use = 'build'",
791+
):
792+
make_variant_dist_info(
793+
vdesc,
794+
variant_info=vinfo,
795+
expand_build_plugin_properties=True,
796+
)

variantlib/api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ def make_variant_dist_info(
202202
filter_plugins=list(build_namespaces),
203203
include_build_plugins=True,
204204
) as plugin_loader:
205-
configs = plugin_loader.get_supported_configs().values()
205+
configs = plugin_loader.get_supported_configs(
206+
require_fixed=True
207+
).values()
206208

207209
for config in configs:
208210
if config.namespace not in build_namespaces:

variantlib/plugins/_subprocess.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,27 @@ def main() -> int:
9090
help="Load specified plugin API",
9191
required=True,
9292
)
93+
parser.add_argument(
94+
"--require-fixed",
95+
action="store_true",
96+
help="Require all plugins to provide fixed supported configs",
97+
)
9398
args = parser.parse_args()
9499
commands = json.load(sys.stdin)
95100
plugins = dict(zip(args.plugin_api, load_plugins(args.plugin_api), strict=True))
96101

102+
if args.require_fixed:
103+
non_fixed_plugins = {
104+
plugin.namespace
105+
for plugin in plugins.values()
106+
if not getattr(plugin, "is_build_plugin", False)
107+
}
108+
if non_fixed_plugins:
109+
raise TypeError(
110+
f"Providers for namespaces {non_fixed_plugins} do not provide fixed "
111+
f"supported configs, they cannot be used with plugin-use = 'build'"
112+
)
113+
97114
retval: dict[str, Any] = {}
98115
for command, command_args in commands.items():
99116
if command == "namespaces":

variantlib/plugins/loader.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def __exit__(
8383
self._namespace_map = None
8484

8585
def _call_subprocess(
86-
self, plugin_apis: list[str], commands: dict[str, Any]
86+
self,
87+
plugin_apis: list[str],
88+
commands: dict[str, Any],
89+
args: Collection[str] = (),
8790
) -> dict[str, Any]:
8891
with TemporaryDirectory(prefix="variantlib") as temp_dir:
8992
# Copy `variantlib/plugins/loader.py` into the temp_dir
@@ -110,7 +113,7 @@ def _call_subprocess(
110113
).read_bytes()
111114
)
112115

113-
args = []
116+
args = [*args]
114117
for plugin_api in plugin_apis:
115118
args += ["--plugin-api", plugin_api]
116119

@@ -184,6 +187,7 @@ def _get_configs(
184187
self,
185188
method: Literal["get_all_configs", "get_supported_configs"],
186189
require_non_empty: bool,
190+
require_fixed: bool,
187191
) -> dict[str, ProviderConfig]:
188192
self._check_plugins_loaded()
189193
assert self._namespace_map is not None
@@ -208,6 +212,7 @@ def _get_configs(
208212
configs = self._call_subprocess(
209213
list(self._namespace_map.keys()),
210214
{method: {}},
215+
args=["--require-fixed"] if require_fixed else [],
211216
)[method]
212217

213218
for plugin_api, plugin_configs in configs.items():
@@ -233,13 +238,20 @@ def get_all_configs(
233238
self,
234239
) -> dict[str, ProviderConfig]:
235240
"""Get a mapping of namespaces to all valid configs"""
236-
return self._get_configs("get_all_configs", require_non_empty=True)
241+
return self._get_configs(
242+
"get_all_configs", require_non_empty=True, require_fixed=False
243+
)
237244

238245
def get_supported_configs(
239246
self,
247+
require_fixed: bool = False,
240248
) -> dict[str, ProviderConfig]:
241249
"""Get a mapping of namespaces to supported configs"""
242-
return self._get_configs("get_supported_configs", require_non_empty=False)
250+
return self._get_configs(
251+
"get_supported_configs",
252+
require_non_empty=False,
253+
require_fixed=require_fixed,
254+
)
243255

244256
@property
245257
def plugin_api_values(self) -> dict[str, str]:

variantlib/protocols.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ def namespace(self) -> VariantNamespace:
6767
"""Plugin namespace"""
6868
raise NotImplementedError
6969

70+
@property
71+
def is_build_plugin(self) -> bool:
72+
"""
73+
Is this plugin valid for `plugin-use = "build"`?
74+
75+
If this is True, then `get_supported_configs()` must always
76+
return the same values, irrespective of the platform used.
77+
This permits the plugin to be used with `plugin-use = "build"`,
78+
where the supported properties are recorded at build time.
79+
80+
If the value of `get_supported_configs()` may change in any way
81+
depending on the platform used, then it must be False
82+
(the default).
83+
"""
84+
return False
85+
7086
@abstractmethod
7187
def get_all_configs(self) -> list[VariantFeatureConfigType]:
7288
"""Get all valid configs for the plugin"""

0 commit comments

Comments
 (0)