11from __future__ import annotations
22
33import pathlib
4- import sys
4+ from collections .abc import MutableMapping , Sequence
5+ from importlib .metadata import EntryPoint
56
67import click
78
@@ -15,7 +16,47 @@ def __dir__() -> list[str]:
1516 return __all__
1617
1718
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 )
1960@click .version_option (__version__ )
2061@click .option (
2162 "--root" ,
@@ -27,26 +68,11 @@ def __dir__() -> list[str]:
2768 writable = True ,
2869 path_type = pathlib .Path ,
2970 ),
30- help = "Path to the python project's root" ,
71+ help = "Path to the Python project's root" ,
3172)
3273@click .pass_context
3374def skbuild (ctx : click .Context , root : pathlib .Path ) -> None : # noqa: ARG001
3475 """
3576 scikit-build Main CLI interface
3677 """
3778 # 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