1
1
from __future__ import annotations
2
2
3
3
import pathlib
4
- import sys
4
+ from collections .abc import MutableMapping , Sequence
5
+ from importlib .metadata import EntryPoint
5
6
6
7
import click
7
8
@@ -15,7 +16,47 @@ def __dir__() -> list[str]:
15
16
return __all__
16
17
17
18
18
- @click .group ("skbuild" )
19
+ class LazyGroup (click .Group ):
20
+ """
21
+ Lazy loader for click commands. Based on Click's documentation, but uses
22
+ EntryPoints.
23
+ """
24
+
25
+ def __init__ (
26
+ self ,
27
+ name : str | None = None ,
28
+ commands : MutableMapping [str , click .Command ]
29
+ | Sequence [click .Command ]
30
+ | None = None ,
31
+ * ,
32
+ lazy_subcommands : Sequence [EntryPoint ] = (),
33
+ ** kwargs : object ,
34
+ ):
35
+ super ().__init__ (name , commands , ** kwargs )
36
+ self .lazy_subcommands = {v .name : v for v in lazy_subcommands }
37
+
38
+ def list_commands (self , ctx : click .Context ) -> list [str ]:
39
+ return sorted ([* super ().list_commands (ctx ), * self .lazy_subcommands ])
40
+
41
+ def get_command (self , ctx : click .Context , cmd_name : str ) -> click .Command | None :
42
+ if cmd_name in self .lazy_subcommands :
43
+ return self ._lazy_load (cmd_name )
44
+ return super ().get_command (ctx , cmd_name )
45
+
46
+ def _lazy_load (self , cmd_name : str ) -> click .Command :
47
+ ep = self .lazy_subcommands [cmd_name ]
48
+ cmd_object = ep .load ()
49
+ if not isinstance (cmd_object , click .Command ):
50
+ msg = f"Lazy loading of { ep } failed by returning a non-command object"
51
+ raise ValueError (msg )
52
+ return cmd_object
53
+
54
+
55
+ # Add all plugin commands.
56
+ CMDS = list (metadata .entry_points (group = "skbuild.commands" ))
57
+
58
+
59
+ @click .group ("skbuild" , cls = LazyGroup , lazy_subcommands = CMDS )
19
60
@click .version_option (__version__ )
20
61
@click .option (
21
62
"--root" ,
@@ -27,26 +68,11 @@ def __dir__() -> list[str]:
27
68
writable = True ,
28
69
path_type = pathlib .Path ,
29
70
),
30
- help = "Path to the python project's root" ,
71
+ help = "Path to the Python project's root" ,
31
72
)
32
73
@click .pass_context
33
74
def skbuild (ctx : click .Context , root : pathlib .Path ) -> None : # noqa: ARG001
34
75
"""
35
76
scikit-build Main CLI interface
36
77
"""
37
78
# TODO: Add specific implementations
38
-
39
-
40
- # Add all plugin commands. Native subcommands are loaded in the package's __init__
41
- for ep in metadata .entry_points (group = "skbuild.commands" ):
42
- try :
43
- # Entry point can either point to a whole module or the decorated command
44
- if not ep .attr :
45
- # If it's a module, just load the module. It should have the necessary `skbuild.command` interface
46
- ep .load ()
47
- else :
48
- # Otherwise assume it is a decorated command that needs to be loaded manually
49
- skbuild .add_command (ep .load ())
50
- except Exception as err :
51
- # TODO: the print should go through the click logging interface
52
- print (f"Could not load cli plugin: { ep } \n { err } " , file = sys .stderr ) # noqa: T201
0 commit comments