Skip to content

Commit 2925e78

Browse files
authored
[NFC] Make subcommand discovery dynamic (#4686)
* [NFC] Make subcommand discovery dynamic The generation of commands.json is eliminated.
1 parent 605e17d commit 2925e78

File tree

3 files changed

+53
-136
lines changed

3 files changed

+53
-136
lines changed

Makefile

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,8 @@ package: package_dir_structure set_git_commit_template package_gerrit_skiplist
6262
$(CC_BUILD_DIR)/config/analyzer_version.json \
6363
$(CC_BUILD_DIR)/config/web_version.json
6464

65-
${PYTHON_BIN} ./scripts/build/create_commands.py -b $(BUILD_DIR) \
66-
--cmd-dir $(ROOT)/codechecker_common/cli_commands \
67-
$(CC_WEB)/codechecker_web/cli \
68-
$(CC_SERVER)/codechecker_server/cli \
69-
$(CC_CLIENT)/codechecker_client/cli \
70-
$(CC_ANALYZER)/codechecker_analyzer/cli \
71-
--bin-file $(ROOT)/bin/CodeChecker
65+
# Copy CodeChecker binary.
66+
cp $(ROOT)/bin/CodeChecker $(CC_BUILD_BIN_DIR)
7267

7368
# Copy license file.
7469
cp $(ROOT)/LICENSE.TXT $(CC_BUILD_DIR)

codechecker_common/cli.py

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@
1111
PYTHON_ARGCOMPLETE_OK
1212
"""
1313

14-
1514
import argparse
16-
from importlib import machinery
15+
import importlib
1716
import io
18-
import json
1917
import os
18+
import pkgutil
2019
import signal
2120
import sys
2221
import 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+
6397
def 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.")

scripts/build/create_commands.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

0 commit comments

Comments
 (0)