diff --git a/tools/cbuild/cbuild.py b/tools/cbuild/cbuild.py index 986ac09..0246867 100644 --- a/tools/cbuild/cbuild.py +++ b/tools/cbuild/cbuild.py @@ -15,6 +15,9 @@ import time import sys import json import re +import concurrent.futures +import os +import locale class ColorFormatter(logging.Formatter): @@ -50,6 +53,111 @@ def setup_logger() -> logging.Logger: logger = setup_logger() +# 简单的翻译系统 +class Translator: + """简单的翻译器""" + + def __init__(self, lang=None): + def detect_language() -> str: + (name, _) = locale.getlocale() + if name is None: + return "en_US" + lower_lang = name.lower() + # 检查是否包含中文相关关键词 + chinese_keywords = [ + "chinese", + "china", + "zh", + "cn", + ] + for keyword in chinese_keywords: + if keyword in lower_lang: + return "zh_CN" + return "en_US" + + self.lang: str = lang if lang is not None else detect_language() + self.translations = { + "zh_CN": { + "build_complete": "完成 {mode} 目标,耗时 {time} (使用 {jobs} 线程)", + "clean_directory": "已清理构建目录: {path}", + "cleaned_files": "清理了 {count} 个文件,总大小 {size}", + "no_build_dir": "没有找到构建目录,无需清理", + "no_targets": "没有可运行的目标", + "running_test": "运行测试: {name}", + "no_tests": "没有找到匹配的测试", + "test_passed": " ✓ 测试 {name} 通过", + "test_failed": " ✗ 测试 {name} 失败", + "test_timeout": " ✗ 测试 {name} 超时", + "test_error": " ✗ 测试 {name} 运行异常: {error}", + "test_results": "测试结果: {passed} 通过, {failed} 失败", + "project_initialized": "项目 '{name}' 初始化完成!", + "project_structure": "项目结构:", + "usage": "使用以下命令构建项目:", + "build_command": " cbuild build", + "run_command": " cbuild run", + "test_command": " cbuild test", + "dependency_tree": "dependency tree:", + "skipping": "跳过 {file}", + "executing_command": "执行命令: {cmd}", + "command_failed": "命令执行失败 [{code}]: {cmd}", + "compiling_file": "编译文件 {file} 时出错: {error}", + "linking_target": "链接目标 {target} 时出错: {error}", + "regex_filter": "使用正则表达式过滤测试: '{pattern}',匹配到 {count} 个测试", + "invalid_regex": "无效的正则表达式 '{pattern}': {error}", + }, + "en_US": { + "build_complete": "Finished {mode} target(s) in {time} (using {jobs} threads)", + "clean_directory": "Cleaned build directory: {path}", + "cleaned_files": "Cleaned {count} files, total size {size}", + "no_build_dir": "No build directory found, nothing to clean", + "no_targets": "No target to run", + "running_test": "Running test: {name}", + "no_tests": "No matching tests found", + "test_passed": " ✓ Test {name} passed", + "test_failed": " ✗ Test {name} failed", + "test_timeout": " ✗ Test {name} timed out", + "test_error": " ✗ Test {name} error: {error}", + "test_results": "Test results: {passed} passed, {failed} failed", + "project_initialized": "Project '{name}' initialized!", + "project_structure": "Project structure:", + "usage": "Use the following commands to build the project:", + "build_command": " cbuild build", + "run_command": " cbuild run", + "test_command": " cbuild test", + "dependency_tree": "dependency tree:", + "skipping": "Skipping {file}", + "executing_command": "Executing command: {cmd}", + "command_failed": "Command failed [{code}]: {cmd}", + "compiling_file": "Error compiling file {file}: {error}", + "linking_target": "Error linking target {target}: {error}", + "regex_filter": "Filtering tests with regex: '{pattern}', matched {count} tests", + "invalid_regex": "Invalid regex '{pattern}': {error}", + }, + } + + def translate(self, key, **kwargs): + """翻译键值""" + lang_dict = self.translations.get(self.lang, self.translations["zh_CN"]) + template = lang_dict.get(key) + if template is None: + return key + if kwargs: + try: + return template.format(**kwargs) + except KeyError: + return template + return template + + def set_language(self, lang): + """设置语言""" + if lang in self.translations: + self.lang = lang + + +# 全局翻译器实例 +translator = Translator(None) + + @dataclass class Dependency: """依赖配置""" @@ -590,7 +698,11 @@ class PackageBuilder: """包构建器""" def __init__( - self, package_path: Path, compiler: Compiler, mode: BuildMode = BuildMode.DEV + self, + package_path: Path, + compiler: Compiler, + mode: BuildMode = BuildMode.DEV, + jobs: int = 0, # 0 表示自动检测 CPU 核心数 ): self.package = PackageConfig(package_path) self.context = BuildContext(self.package, mode) @@ -598,6 +710,11 @@ class PackageBuilder: self.cache = BuildCache(self.context.paths.output) self.mode = mode self.flags = self.compiler.get_flags(mode) + self.jobs = jobs if jobs > 0 else self._get_cpu_count() + + def _get_cpu_count(self) -> int: + """获取 CPU 核心数""" + return os.cpu_count() or 1 def _compile(self, source: Path, output: Path): """编译单个源文件""" @@ -607,7 +724,73 @@ class PackageBuilder: ) self.cache.update(source, output) else: - logger.debug("跳过 %s", source) + logger.debug(translator.translate("skipping", file=str(source))) + + def _parallel_compile(self, compile_tasks: list[tuple[Path, Path]]) -> list[Path]: + """并行编译多个源文件""" + if not compile_tasks: + return [] + + # 如果只有一个文件或线程数为1,使用串行编译 + if len(compile_tasks) == 1 or self.jobs == 1: + objects = [] + for src, obj in compile_tasks: + self._compile(src, obj) + objects.append(obj) + return objects + + # 使用线程池并行编译 + objects = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=self.jobs) as executor: + # 提交所有编译任务 + future_to_task = {} + for src, obj in compile_tasks: + future = executor.submit(self._compile, src, obj) + future_to_task[future] = (src, obj) + + # 等待所有任务完成 + for future in concurrent.futures.as_completed(future_to_task): + src, obj = future_to_task[future] + try: + future.result() # 检查是否有异常 + objects.append(obj) + except Exception as e: + logger.error("编译文件 %s 时出错: %s", src, e) + raise + + return objects + + def _parallel_link(self, link_tasks: list[tuple[Target, list[Path]]]): + """并行链接多个目标""" + if not link_tasks: + return + + # 如果只有一个链接任务或线程数为1,使用串行链接 + if len(link_tasks) == 1 or self.jobs == 1: + for target, objects in link_tasks: + objects.append(target.object) + self.compiler.link(objects, target.output, self.flags) + return + + # 使用线程池并行链接 + with concurrent.futures.ThreadPoolExecutor(max_workers=self.jobs) as executor: + # 提交所有链接任务 + future_to_task = {} + for target, objects in link_tasks: + objects.append(target.object) + future = executor.submit( + self.compiler.link, objects, target.output, self.flags + ) + future_to_task[future] = target + + # 等待所有任务完成 + for future in concurrent.futures.as_completed(future_to_task): + target = future_to_task[future] + try: + future.result() # 检查是否有异常 + except Exception as e: + logger.error("链接目标 %s 时出错: %s", target.name, e) + raise def build(self, target_types: list[TargetType]): """构建包""" @@ -618,14 +801,16 @@ class PackageBuilder: self.context.paths.output.mkdir(parents=True, exist_ok=True) self.context.paths.objects.mkdir(parents=True, exist_ok=True) - # 编译所有依赖的源文件 - objects = [] - for src, obj in self.context.get_compile_objects(): - self._compile(src, obj) - objects.append(obj) + # 获取需要编译的文件列表 + compile_tasks = self.context.get_compile_objects() + + # 使用线程池并行编译 + objects = self._parallel_compile(compile_tasks) # 构建目标 exec_types = {TargetType.MAIN_EXEC, TargetType.EXEC, TargetType.TEST_EXEC} + link_tasks = [] + for target in self.context.get_targets(): if target.type not in target_types: continue @@ -633,14 +818,17 @@ class PackageBuilder: continue self._compile(target.source, target.object) - objects.append(target.object) - self.compiler.link(objects, target.output, self.flags) - objects.pop() + link_tasks.append((target, objects.copy())) + + # 并行链接 + self._parallel_link(link_tasks) self.cache.save() elapsed = time.time() - start time_str = f"{elapsed:.2f}s" if elapsed < 60 else f"{elapsed / 60:.1f}m" - logger.info("完成 %s 目标,耗时 %s", self.mode.value, time_str) + logger.info( + "完成 %s 目标,耗时 %s (使用 %d 线程)", self.mode.value, time_str, self.jobs + ) def run(self): """运行主程序""" @@ -830,6 +1018,9 @@ def create_parser(): "--compiler", "-c", choices=["gcc", "clang"], default="gcc", help="编译器" ) subparser.add_argument("--record", "-r", action="store_true", help="记录命令") + subparser.add_argument( + "--jobs", "-j", type=int, default=0, help="并行编译任务数 (0=自动检测)" + ) mode_group = subparser.add_mutually_exclusive_group() mode_group.add_argument( "--debug", @@ -919,8 +1110,11 @@ def main(): # 获取构建模式 mode = getattr(args, "mode", BuildMode.DEV) + # 获取线程数 + jobs = getattr(args, "jobs", 0) + # 创建构建器 - builder = PackageBuilder(Path(args.path), compiler, mode) + builder = PackageBuilder(Path(args.path), compiler, mode, jobs) # 执行命令 if args.command == "build":