1111PYTHON_ARGCOMPLETE_OK
1212"""
1313
14-
1514import argparse
16- from importlib import machinery
15+ import importlib
1716import io
18- import json
1917import os
18+ import pkgutil
2019import signal
2120import sys
2221import argcomplete
@@ -34,32 +33,67 @@ def error(self, message):
3433 self .exit (1 , f"{ self .prog } : error: { message } \n " )
3534
3635
37- def add_subcommand (subparsers , sub_cmd , cmd_module_path , lib_dir_path ):
36+ def add_subcommand (subparsers , sub_cmd , module_name ):
3837 """
3938 Load the subcommand module and then add the subcommand to the available
4039 subcommands in the given subparsers collection.
4140
4241 subparsers has to be the return value of the add_parsers() method on an
4342 argparse.ArgumentParser.
4443 """
45- m_path , m_name = os .path .split (cmd_module_path )
46-
47- module_name = os .path .splitext (m_name )[0 ]
48- target = [os .path .join (lib_dir_path , m_path )]
49-
50- # Load the module named as the argument.
51- cmd_spec = machinery .PathFinder ().find_spec (module_name ,
52- target )
53- command_module = cmd_spec .loader .load_module (module_name )
44+ try :
45+ # Import the module directly using importlib
46+ command_module = importlib .import_module (module_name )
47+ except ImportError as e :
48+ raise ImportError (
49+ f"Subcommand module '{ module_name } ' not found: { e } " ) from e
5450
5551 # Now that the module is loaded, construct an ArgumentParser for it.
5652 sc_parser = subparsers .add_parser (
57- sub_cmd , ** command_module .get_argparser_ctor_args ())
53+ sub_cmd , ** command_module .get_argparser_ctor_args ()
54+ )
5855
5956 # Run the method which adds the arguments to the subcommand's handler.
6057 command_module .add_arguments_to_parser (sc_parser )
6158
6259
60+ def discover_subcommands ():
61+ """Discover available subcommands based on the modules in the project."""
62+ subcmds = {}
63+
64+ # Define the packages to search for subcommands
65+ packages_to_search = [
66+ "codechecker_common.cli_commands" ,
67+ "codechecker_analyzer.cli" ,
68+ "codechecker_web.cli" ,
69+ "codechecker_server.cli" ,
70+ "codechecker_client.cli" ,
71+ ]
72+
73+ for package_name in packages_to_search :
74+ try :
75+ package = importlib .import_module (package_name )
76+
77+ # Use pkgutil to find all modules in the package
78+ for _ , modname , _ in pkgutil .iter_modules (
79+ package .__path__ , package .__name__ + "."
80+ ):
81+ if "__" in modname :
82+ continue
83+
84+ # Convert module name to command name
85+ # (e.g., "analyzer_version.py" -> "analyzer-version"
86+ cmd_name = modname .split ("." )[- 1 ].replace ("_" , "-" )
87+ subcmds [cmd_name ] = modname
88+
89+ except ImportError as e :
90+ # logger is not available yet, so use print to stderr
91+ print (f"Package { package_name } not found: { e } " , file = sys .stderr )
92+ continue
93+
94+ return subcmds
95+
96+
6397def get_data_files_dir_path ():
6498 """ Get data files directory path """
6599 bin_dir = os .environ .get ('CC_BIN_DIR' )
@@ -124,13 +158,7 @@ def main():
124158 data_files_dir_path = get_data_files_dir_path ()
125159 os .environ ['CC_DATA_FILES_DIR' ] = data_files_dir_path
126160
127- # Load the available CodeChecker subcommands.
128- # This list is generated dynamically by scripts/build_package.py, and is
129- # always meant to be available alongside the CodeChecker.py.
130- commands_cfg = os .path .join (data_files_dir_path , "config" , "commands.json" )
131-
132- with open (commands_cfg , encoding = "utf-8" , errors = "ignore" ) as cfg_file :
133- subcommands = json .load (cfg_file )
161+ subcommands = discover_subcommands ()
134162
135163 def signal_handler (signum , _ ):
136164 """
@@ -185,11 +213,9 @@ def signal_handler(signum, _):
185213 # Consider only the given command as an available one.
186214 subcommands = {first_command : subcommands [first_command ]}
187215
188- lib_dir_path = os .environ .get ('CC_LIB_DIR' )
189- for subcommand in subcommands :
216+ for subcommand , module_name in subcommands .items ():
190217 try :
191- add_subcommand (subparsers , subcommand ,
192- subcommands [subcommand ], lib_dir_path )
218+ add_subcommand (subparsers , subcommand , module_name )
193219 except (IOError , ImportError ):
194220 print ("Couldn't import module for subcommand '" +
195221 subcommand + "'... ignoring." )
0 commit comments