feat(cbuild): 重构依赖解析器并增强命令行功能
- 将 `resolved_deps` 重命名为 `deps` 并新增 `dep_map` 用于存储依赖关系 - 新增 `get_dependencies_of` 方法以获取指定包的直接依赖列表 - 实现递归打印依赖树的功能,优化 `tree` 命令展示效果 - 引入分层命令行参数解析,支持子命令及更多选项(如 --record、--args 等) - 改进日志输出与构建模式提示信息,使其更准确反映当前构建状态 - 编译器类中增加命令记录机制,便于调试和回溯执行过程
This commit is contained in:
@@ -61,9 +61,9 @@
|
|||||||
*/
|
*/
|
||||||
#define vec_push(vec, value) \
|
#define vec_push(vec, value) \
|
||||||
do { \
|
do { \
|
||||||
if (vec.size >= vec.cap) { \
|
if ((vec).size >= (vec).cap) { \
|
||||||
int cap = vec.cap ? vec.cap * 2 : 4; \
|
int cap = (vec).cap ? (vec).cap * 2 : 4; \
|
||||||
void *data = __vec_realloc(vec.data, cap * sizeof(*vec.data)); \
|
void *data = __vec_realloc((vec).data, cap * sizeof(*(vec).data)); \
|
||||||
if (!data) { \
|
if (!data) { \
|
||||||
LOG_FATAL("vector_push: realloc failed\n"); \
|
LOG_FATAL("vector_push: realloc failed\n"); \
|
||||||
} \
|
} \
|
||||||
|
|||||||
@@ -194,14 +194,15 @@ class DependencyResolver:
|
|||||||
|
|
||||||
def __init__(self, root_package: PackageConfig):
|
def __init__(self, root_package: PackageConfig):
|
||||||
self.root_package = root_package
|
self.root_package = root_package
|
||||||
self.resolved_deps: dict[str, PackageConfig] = {}
|
self.deps: dict[str, PackageConfig] = {}
|
||||||
self.deps_graph: TopologicalSorter = TopologicalSorter()
|
self.deps_graph: TopologicalSorter = TopologicalSorter()
|
||||||
|
self.dep_map: dict[str, list[str]] = {}
|
||||||
self._resolved = False
|
self._resolved = False
|
||||||
|
|
||||||
def resolve(self) -> dict[str, PackageConfig]:
|
def resolve(self) -> dict[str, PackageConfig]:
|
||||||
"""解析所有依赖"""
|
"""解析所有依赖"""
|
||||||
if self._resolved:
|
if self._resolved:
|
||||||
return self.resolved_deps
|
return self.deps.copy()
|
||||||
|
|
||||||
# 使用广度优先搜索解析所有依赖
|
# 使用广度优先搜索解析所有依赖
|
||||||
queue = [self.root_package]
|
queue = [self.root_package]
|
||||||
@@ -216,8 +217,11 @@ class DependencyResolver:
|
|||||||
visited.add(pkg_name)
|
visited.add(pkg_name)
|
||||||
|
|
||||||
# 创建构建上下文
|
# 创建构建上下文
|
||||||
if pkg_name not in self.resolved_deps:
|
if pkg_name not in self.deps:
|
||||||
self.resolved_deps[pkg_name] = pkg_config
|
self.deps[pkg_name] = pkg_config
|
||||||
|
|
||||||
|
if pkg_name not in self.dep_map:
|
||||||
|
self.dep_map[pkg_name] = []
|
||||||
|
|
||||||
# 解析直接依赖
|
# 解析直接依赖
|
||||||
for dep in pkg_config.dependencies:
|
for dep in pkg_config.dependencies:
|
||||||
@@ -227,14 +231,26 @@ class DependencyResolver:
|
|||||||
|
|
||||||
# 添加依赖图关系
|
# 添加依赖图关系
|
||||||
self.deps_graph.add(pkg_name, dep_name)
|
self.deps_graph.add(pkg_name, dep_name)
|
||||||
|
self.dep_map[pkg_name].append(dep_name)
|
||||||
|
|
||||||
# 如果是新依赖,加入队列继续解析
|
# 如果是新依赖,加入队列继续解析
|
||||||
if dep_name not in visited and dep_name not in self.resolved_deps:
|
if dep_name not in visited and dep_name not in self.deps:
|
||||||
queue.append(dep_config)
|
queue.append(dep_config)
|
||||||
|
|
||||||
self.deps_graph.prepare()
|
self.deps_graph.prepare()
|
||||||
self._resolved = True
|
self._resolved = True
|
||||||
return self.resolved_deps
|
return self.deps.copy()
|
||||||
|
|
||||||
|
def get_dependencies_of(self, package_name: str) -> list[str]:
|
||||||
|
"""获取指定包的直接依赖列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package_name (str): 包名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: 直接依赖的包名列表
|
||||||
|
"""
|
||||||
|
return self.dep_map.get(package_name, [])
|
||||||
|
|
||||||
def get_sorted_dependencies(self) -> list[CBuildContext]:
|
def get_sorted_dependencies(self) -> list[CBuildContext]:
|
||||||
"""获取按拓扑排序的依赖列表(不包括根包)"""
|
"""获取按拓扑排序的依赖列表(不包括根包)"""
|
||||||
@@ -243,9 +259,9 @@ class DependencyResolver:
|
|||||||
|
|
||||||
sorted_names = list(self.deps_graph.static_order())
|
sorted_names = list(self.deps_graph.static_order())
|
||||||
return [
|
return [
|
||||||
CBuildContext(self.resolved_deps[name])
|
CBuildContext(self.deps[name])
|
||||||
for name in sorted_names
|
for name in sorted_names
|
||||||
if name != self.root_package.name and name in self.resolved_deps
|
if name != self.root_package.name and name in self.deps
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_all_contexts(self) -> list[CBuildContext]:
|
def get_all_contexts(self) -> list[CBuildContext]:
|
||||||
@@ -253,9 +269,7 @@ class DependencyResolver:
|
|||||||
if not self._resolved:
|
if not self._resolved:
|
||||||
self.resolve()
|
self.resolve()
|
||||||
|
|
||||||
return [
|
return [CBuildContext(resolved_dep) for resolved_dep in self.deps.values()]
|
||||||
CBuildContext(resolved_dep) for resolved_dep in self.resolved_deps.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -556,6 +570,20 @@ class CompilerBuildMode(Enum):
|
|||||||
class Compiler(ABC):
|
class Compiler(ABC):
|
||||||
"""编译器抽象类"""
|
"""编译器抽象类"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.recorded_commands: list[str] = []
|
||||||
|
self.should_record = False
|
||||||
|
|
||||||
|
def enable_recording(self, enable=True):
|
||||||
|
"""启用或禁用命令记录"""
|
||||||
|
self.should_record = enable
|
||||||
|
if enable:
|
||||||
|
self.recorded_commands.clear()
|
||||||
|
|
||||||
|
def get_recorded_commands(self):
|
||||||
|
"""获取记录的命令列表"""
|
||||||
|
return self.recorded_commands.copy()
|
||||||
|
|
||||||
def get_default_flags(self, mode: CompilerBuildMode) -> list[str]:
|
def get_default_flags(self, mode: CompilerBuildMode) -> list[str]:
|
||||||
"""获取指定模式的默认标志"""
|
"""获取指定模式的默认标志"""
|
||||||
logger.debug("get default flags for mode: %s is not supported", mode.name)
|
logger.debug("get default flags for mode: %s is not supported", mode.name)
|
||||||
@@ -575,6 +603,10 @@ class Compiler(ABC):
|
|||||||
|
|
||||||
def cmd(self, cmd: str | list[str]):
|
def cmd(self, cmd: str | list[str]):
|
||||||
"""执行命令并处理错误输出"""
|
"""执行命令并处理错误输出"""
|
||||||
|
if self.should_record:
|
||||||
|
self.recorded_commands.append(
|
||||||
|
" ".join(cmd) if isinstance(cmd, list) else cmd
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"command: `%s`", " ".join(cmd) if isinstance(cmd, list) else cmd
|
"command: `%s`", " ".join(cmd) if isinstance(cmd, list) else cmd
|
||||||
@@ -683,6 +715,7 @@ class CPackageBuilder:
|
|||||||
self.compiler: Compiler = compiler
|
self.compiler: Compiler = compiler
|
||||||
# FIXME hack context
|
# FIXME hack context
|
||||||
self.cache = BuildCache(self.context.path.output_path) if need_cache else None
|
self.cache = BuildCache(self.context.path.output_path) if need_cache else None
|
||||||
|
self.mode = mode
|
||||||
self.global_flags = self.compiler.get_default_flags(mode)
|
self.global_flags = self.compiler.get_default_flags(mode)
|
||||||
|
|
||||||
def _compile(
|
def _compile(
|
||||||
@@ -767,9 +800,7 @@ class CPackageBuilder:
|
|||||||
f"{elapsed_time:.2f}s" if elapsed_time < 60 else f"{elapsed_time / 60:.1f}m"
|
f"{elapsed_time:.2f}s" if elapsed_time < 60 else f"{elapsed_time / 60:.1f}m"
|
||||||
)
|
)
|
||||||
# 显示完成信息
|
# 显示完成信息
|
||||||
logger.info(
|
logger.info("Finished %s target(s) in %s", self.mode.value, time_str)
|
||||||
"\tFinished dev [unoptimized + debuginfo] target(s) in %s", time_str
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""运行项目"""
|
"""运行项目"""
|
||||||
@@ -805,28 +836,40 @@ class CPackageBuilder:
|
|||||||
logger.info("没有找到构建目录,无需清理")
|
logger.info("没有找到构建目录,无需清理")
|
||||||
|
|
||||||
def tree(self):
|
def tree(self):
|
||||||
"""打印构建树 - 仿照 cargo 的风格"""
|
"""打印构建树"""
|
||||||
# TODO 递归显示
|
|
||||||
print("dependency tree:")
|
print("dependency tree:")
|
||||||
|
|
||||||
# 解析依赖
|
# 解析依赖
|
||||||
resolver = self.context.deps_resolver
|
deps = self.context.deps_resolver.resolve() # 确保依赖已解析
|
||||||
resolver.resolve()
|
root_pkg = self.context.deps_resolver.root_package
|
||||||
|
|
||||||
# 获取根包
|
def print_tree(pkg_name, prefix="", is_last=True):
|
||||||
root_pkg = resolver.root_package
|
"""递归打印依赖树"""
|
||||||
print(f"{root_pkg.name} v{root_pkg.version}")
|
|
||||||
|
|
||||||
# 获取依赖图的节点和边
|
# 获取包配置
|
||||||
# 这里我们简化处理,只显示直接依赖
|
pkg_config = deps[pkg_name]
|
||||||
for pkg_config in resolver.resolved_deps.values():
|
|
||||||
if pkg_config.name != root_pkg.name: # 跳过根包
|
# 打印当前包
|
||||||
indent = (
|
if pkg_name == root_pkg.name:
|
||||||
"├── "
|
print(f"{pkg_config.name} v{pkg_config.version}")
|
||||||
if pkg_config.name != list(resolver.resolved_deps.keys())[-1]
|
else:
|
||||||
else "└── "
|
connector = "└── " if is_last else "├── "
|
||||||
)
|
print(f"{prefix}{connector}{pkg_config.name} v{pkg_config.version}")
|
||||||
print(f"{indent}{pkg_config.name} v{pkg_config.version}")
|
|
||||||
|
# 计算子节点的前缀
|
||||||
|
if pkg_name == root_pkg.name:
|
||||||
|
child_prefix = ""
|
||||||
|
else:
|
||||||
|
child_prefix = prefix + (" " if is_last else "│ ")
|
||||||
|
|
||||||
|
# 递归打印依赖
|
||||||
|
dependencies = self.context.deps_resolver.get_dependencies_of(pkg_name)
|
||||||
|
for i, dep_name in enumerate(dependencies):
|
||||||
|
is_last_child = i == len(dependencies) - 1
|
||||||
|
print_tree(dep_name, child_prefix, is_last_child)
|
||||||
|
|
||||||
|
# 从根包开始打印树
|
||||||
|
print_tree(root_pkg.name)
|
||||||
|
|
||||||
def tests(self):
|
def tests(self):
|
||||||
"""运行测试"""
|
"""运行测试"""
|
||||||
@@ -867,47 +910,14 @@ class CPackageBuilder:
|
|||||||
failed += 1
|
failed += 1
|
||||||
print(f"\n测试结果: {passed} 通过, {failed} 失败")
|
print(f"\n测试结果: {passed} 通过, {failed} 失败")
|
||||||
|
|
||||||
def cmd(self, cmd: str):
|
|
||||||
"""执行命令"""
|
|
||||||
match cmd:
|
|
||||||
case "build":
|
|
||||||
self.build(
|
|
||||||
[
|
|
||||||
TargetType.MAIN_EXECUTABLE,
|
|
||||||
TargetType.TEST_EXECUTABLE,
|
|
||||||
TargetType.STATIC_LIBRARY,
|
|
||||||
TargetType.SHARED_LIBRARY,
|
|
||||||
TargetType.EXECUTABLE,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
case "run":
|
|
||||||
bin_path = self.context.path.default_bin_path
|
|
||||||
if not bin_path.exists():
|
|
||||||
logger.error("%s not exist", bin_path)
|
|
||||||
return
|
|
||||||
self.build([TargetType.MAIN_EXECUTABLE])
|
|
||||||
self.run()
|
|
||||||
case "test":
|
|
||||||
self.build([TargetType.TEST_EXECUTABLE])
|
|
||||||
self.tests()
|
|
||||||
case "clean":
|
|
||||||
self.clean()
|
|
||||||
case "tree":
|
|
||||||
self.tree()
|
|
||||||
case _:
|
|
||||||
logger.error("unknown command: %s", cmd)
|
|
||||||
|
|
||||||
|
def setup_argparse():
|
||||||
def main():
|
"""设置分级命令行参数解析器"""
|
||||||
"""main"""
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Simple C Package Manager", prog="cbuild"
|
description="Simple C Package Manager", prog="cbuild"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"command",
|
# 全局选项
|
||||||
choices=["build", "run", "test", "clean", "tree"],
|
|
||||||
help="Command to execute",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--compiler",
|
"--compiler",
|
||||||
"-c",
|
"-c",
|
||||||
@@ -919,7 +929,7 @@ def main():
|
|||||||
"--verbose",
|
"--verbose",
|
||||||
"-V",
|
"-V",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="enable the logging to debug (default: false)",
|
help="Enable verbose logging (default: false)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--path",
|
"--path",
|
||||||
@@ -927,15 +937,55 @@ def main():
|
|||||||
default=".",
|
default=".",
|
||||||
help="Path to the package (default: current directory)",
|
help="Path to the package (default: current directory)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--record",
|
||||||
|
"-r",
|
||||||
|
action="store_true",
|
||||||
|
help="Record the compiler command and output to stdout",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建子命令解析器
|
||||||
|
subparsers = parser.add_subparsers(
|
||||||
|
dest="command", help="Available commands", metavar="COMMAND"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build 子命令
|
||||||
|
subparsers.add_parser("build", help="Build the project")
|
||||||
|
|
||||||
|
# Run 子命令
|
||||||
|
run_parser = subparsers.add_parser("run", help="Build and run the project")
|
||||||
|
run_parser.add_argument(
|
||||||
|
"--args", nargs=argparse.REMAINDER, help="Arguments to pass to the program"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test 子命令
|
||||||
|
test_parser = subparsers.add_parser("test", help="Build and run tests")
|
||||||
|
test_parser.add_argument("--filter", help="Filter tests by name pattern")
|
||||||
|
|
||||||
|
# Clean 子命令
|
||||||
|
clean_parser = subparsers.add_parser("clean", help="Clean build artifacts")
|
||||||
|
clean_parser.add_argument(
|
||||||
|
"--all", action="store_true", help="Clean all including cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tree 子命令
|
||||||
|
subparsers.add_parser("tree", help="Show dependency tree")
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""main function with hierarchical command processing"""
|
||||||
|
parser = setup_argparse()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
package_path = Path.cwd() / Path(args.path)
|
# 设置日志级别
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
else:
|
else:
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# 初始化编译器
|
||||||
match args.compiler:
|
match args.compiler:
|
||||||
case "gcc":
|
case "gcc":
|
||||||
compiler = GccCompiler()
|
compiler = GccCompiler()
|
||||||
@@ -946,8 +996,50 @@ def main():
|
|||||||
raise ValueError("Invalid compiler")
|
raise ValueError("Invalid compiler")
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Invalid compiler")
|
raise ValueError("Invalid compiler")
|
||||||
|
|
||||||
|
# 启用命令记录
|
||||||
|
if args.record:
|
||||||
|
compiler.enable_recording()
|
||||||
|
|
||||||
|
# 创建构建器
|
||||||
|
package_path = Path.cwd() / Path(args.path)
|
||||||
builder = CPackageBuilder(package_path, compiler)
|
builder = CPackageBuilder(package_path, compiler)
|
||||||
builder.cmd(args.command)
|
|
||||||
|
# 处理子命令
|
||||||
|
match args.command:
|
||||||
|
case "build":
|
||||||
|
builder.build(
|
||||||
|
[
|
||||||
|
TargetType.MAIN_EXECUTABLE,
|
||||||
|
TargetType.TEST_EXECUTABLE,
|
||||||
|
TargetType.STATIC_LIBRARY,
|
||||||
|
TargetType.SHARED_LIBRARY,
|
||||||
|
TargetType.EXECUTABLE,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
case "run":
|
||||||
|
bin_path = builder.context.path.default_bin_path
|
||||||
|
if not bin_path.exists():
|
||||||
|
logger.error("%s not exist", bin_path)
|
||||||
|
return
|
||||||
|
builder.build([TargetType.MAIN_EXECUTABLE])
|
||||||
|
builder.run()
|
||||||
|
case "test":
|
||||||
|
builder.build([TargetType.TEST_EXECUTABLE])
|
||||||
|
builder.tests()
|
||||||
|
case "clean":
|
||||||
|
builder.clean()
|
||||||
|
case "tree":
|
||||||
|
builder.tree()
|
||||||
|
case _:
|
||||||
|
logger.error("unknown command: %s", args.command)
|
||||||
|
|
||||||
|
# 输出记录的命令(如果有)
|
||||||
|
if args.record:
|
||||||
|
cmds = compiler.get_recorded_commands()
|
||||||
|
logger.info("Recorded compiler commands: [len is %s]", len(cmds))
|
||||||
|
for cmd in cmds:
|
||||||
|
print(cmd)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user