资讯详情

资讯详情

【EDK2】在UDK2018中实现兼容Vscode中的Edk2Code插件:把compile_commands.json改到TaoToken

1. UDK2018 里 Edk2Code 插件为什么补全和跳转全失效如果你正在维护基于 UDK2018 的 EDK2 项目大概率遇到过这个场景Vscode 里装了 Edk2Code 插件打开MdeModulePkg下的某个.c文件gBS-后面敲一半没有任何提示Ctrl左键点EFI_STATUS也跳不进去整个工程像一潭死水。这不是插件坏了而是 UDK2018 这套老源码树根本没有产出插件需要的编译数据库。EDK2 的代码补全和符号跳转依赖两个东西一个是compile_commands.json它告诉语言服务每个源文件用什么编译命令、带哪些-I头文件路径另一个是 Edk2Code 插件自己维护的索引数据库它记录模块、库、PCD、Protocol 之间的引用关系。新版 EDK2 已经把compile_commands.json的生成做进了BaseTools/Source/Python/build/BuildReport.py通过-Y COMPILE_INFO -y BuildReport.log这个开关触发。但 UDK2018 的build.py里-Y的choices列表压根没有COMPILE_INFO这一项你敲上去它会直接报参数非法。所以问题的根子有两层。第一层是构建系统层面UDK2018 不认-Y COMPILE_INFO你得手动给BuildReport.py、build.py、Common/Datatype.py打补丁让它能吐出compile_commands.json。第二层是插件请求层面Edk2Code 在重建索引、做语义分析时会向配置的 endpoint 发请求默认走的是公共通道Key 和模型都不受你控制遇到限流或者模型切换就会卡住。把这条请求链路改到 TaoToken 的统一 Key/API 通道索引重建和补全请求就稳定了。这篇内容就是围绕这两层来写的。前半段交付可复制的补丁和生成脚本让 UDK2018 产出compile_commands.json后半段交付 Vscode 的settings.json片段和 TaoToken 配置项把插件请求的 endpoint 指过去最后给出索引重建、跳转验证和报错排查的逐步动作。适合正在用 UDK2018 做固件开发、被 Vscode 补全折磨过的同学。2. 给 UDK2018 打补丁生成 compile_commands.json 的完整步骤这一章是整篇的技术核心篇幅会比其他章长。我按文件逐个说每个改动都给出可复制的代码片段你照着改完就能跑。2.1 改动 BuildReport.py 增加 GenerateCompileInfo 方法文件路径是BaseTools/Source/Python/build/BuildReport.py。先在文件顶部的 import 区补三行如果已经有相同的 import 就跳过import json import re import os from Common.Misc import SaveFileOnChange from Common.DataType import TAB_COMPILER_MSFT然后在class BuildReport()内部添加GenerateCompileInfo方法。这个方法遍历self.ReportList从每个AutoGenObjectList里取出模块和库收集源文件、头文件路径、编译规则拼出 compilation database 条目。核心逻辑如下def GenerateCompileInfo(self): 生成供 IDE/clangd/vscode 使用的编译数据库。 输出目录Build/BuildTarget/ToolChain/CompileInfo/ 输出文件compile_commands.json, cscope.files, module_report.json try: compile_commands [] used_files set() module_report [] for (Wa, MaList) in self.ReportList: try: for fp in Wa._GetMetaFiles(Wa.BuildTarget, Wa.ToolChain): used_files.add(fp) except Exception: pass for autogen in Wa.AutoGenObjectList: for module in (autogen.LibraryAutoGenList autogen.ModuleAutoGenList): used_files.add(module.MetaFile.Path) md { Name: module.Name, Arch: module.Arch, Path: module.MetaFile.Path, Guid: getattr(module, Guid, ), BuildType: getattr(module, BuildType, ), IsLibrary: getattr(module, IsLibrary, False), SourceDir: getattr(module, SourceDir, ), Files: [], Libraries: [], Packages: [], PPI: [], Protocol: [], Pcd: [] } for sf in module.SourceFileList: md[Files].append({Name: sf.Name, Path: sf.Path}) for libag in getattr(module, LibraryAutoGenList, []): md[Libraries].append({Path: libag.MetaFile.Path}) for pkg in getattr(module, PackageList, []): entry {Path: pkg.MetaFile.Path, Includes: []} for inc in getattr(pkg, Includes, []): entry[Includes].append(inc.Path) md[Packages].append(entry) for k in getattr(module, PpiList, {}).keys(): md[PPI].append({Name: k, Guid: module.PpiList[k]}) for k in getattr(module, ProtocolList, {}).keys(): md[Protocol].append({Name: k, Guid: module.ProtocolList[k]}) for pcd in getattr(module, LibraryPcdList, []): md[Pcd].append({ Space: getattr(pcd, TokenSpaceGuidCName, ), Name: getattr(pcd, TokenCName, ), Value: getattr(pcd, TokenValue, ), Guid: getattr(pcd, TokenSpaceGuidValue, ), DatumType: getattr(pcd, DatumType, ), Type: getattr(pcd, Type, ), DefaultValue: getattr(pcd, DefaultValue, ) }) module_report.append(md) inc_flag /I if module.BuildRuleFamily TAB_COMPILER_MSFT else -I for src in module.SourceFileList: used_files.add(src.Path) if src.Ext not in [.c, .cc, .cpp, .cxx]: continue try: rule_cmd module.BuildRules[src.Ext].CommandList[0] except Exception: continue def _expand_var(m): token m.group(1) parts token.split(_) try: if len(parts) 1: return module.BuildOption[parts[0]][PATH] else: return module.BuildOption[parts[0]][parts[1]] except Exception: return build_cmd re.sub(r\$\((.*?)\), _expand_var, rule_cmd) try: incs getattr(module, IncludePathList, []) inc_blob .join([ (inc_flag \ inc \) if in inc else (inc_flag inc) for inc in incs ]) build_cmd build_cmd.replace(${src}, inc_blob) build_cmd build_cmd.replace(${dst}, getattr(module, OutputDir, )) except Exception: pass build_cmd re.sub(r\$\((?:.*?)\), , build_cmd).strip() entry { file: src.Path, directory: src.Dir, command: build_cmd } compile_commands.append(entry) compile_info_dir os.path.join(Wa.BuildDir, CompileInfo) if not os.path.isdir(compile_info_dir): try: os.makedirs(compile_info_dir) except Exception: pass compile_commands.sort(keylambda x: x[file]) SaveFileOnChange(os.path.join(compile_info_dir, compile_commands.json), json.dumps(compile_commands, indent2), False) SaveFileOnChange(os.path.join(compile_info_dir, cscope.files), \n.join(sorted(used_files)), False) module_report.sort(keylambda x: x[Path]) SaveFileOnChange(os.path.join(compile_info_dir, module_report.json), json.dumps(module_report, indent2), False) except Exception: from Common import EdkLogger import traceback, platform, sys EdkLogger.error(BuildReport, 0, Unknown fatal error when generating compile information, ExtraDatagetattr(self, ReportFile, None), RaiseErrorFalse) EdkLogger.quiet((Python %s on %s\n%s) % ( platform.python_version(), sys.platform, traceback.format_exc()))注意compile_commands.json里的file字段用的是绝对路径directory是源文件所在目录command是展开变量后的编译命令。这样 clangd 和 C/C 插件都能正确解析。2.2 在 GenerateReport 入口挂钩调用找到def GenerateReport(self, BuildDuration, AutoGenTime, MakeTime, GenFdsTime):在它写日志和报表的 try 块开头加一行判断if COMPILE_INFO in self.ReportType: self.GenerateCompileInfo()这一行的作用是只有当你传了-Y COMPILE_INFO时才生成编译数据库不影响正常构建流程。2.3 改动 build.py 让 -Y 接受 COMPILE_INFO文件路径BaseTools/Source/Python/build/build.py。找到-Y/--report-type的选项定义把COMPILE_INFO加进choices列表Parser.add_option(-Y, --report-type, actionappend, typechoice, choices[PCD, LIBRARY, FLASH, DEPEX, BUILD_FLAGS, FIXED_ADDRESS, HASH, EXECUTION_ORDER, COMPILE_INFO], destReportType, default[], helpFlags that control the type of build report to generate. Must be one of: [PCD, LIBRARY, FLASH, DEPEX, BUILD_FLAGS, FIXED_ADDRESS, HASH, EXECUTION_ORDER, COMPILE_INFO]. )2.4 改动 Common/Datatype.py 补常量文件路径BaseTools/Source/Python/Common/Datatype.py在文件最后一行加上TAB_COMPILER_MSFT MSFT这个常量在GenerateCompileInfo里用来判断编译器家族MSVC 用/IGCC 用-I。2.5 编译验证生成结果改完三个文件后先执行edksetup.bat然后build -h看一下-Y的选项里有没有COMPILE_INFO。有的话说明 build 走的是 Python 脚本补丁生效了。正常编译命令加上-Y COMPILE_INFO -y BuildReport.logbuild -p YourPkg/YourPkg.dsc -a IA32 -t VS2015x86 -b DEBUG -Y COMPILE_INFO -y BuildReport.log注意 UDK2018 要用 Python 2.7 编译别用 Python 3。编译完成后在Build/NT32IA32/DEBUG_VS2015x86/CompileInfo/目录下应该能看到三个文件compile_commands.json、cscope.files、module_report.json。如果compile_commands.json是空的检查一下module.SourceFileList是否为空通常是BuildRules没匹配上源文件扩展名。3. Vscode settings.json 与 TaoToken 配置项怎么填拿到compile_commands.json只是第一步接下来要让 Edk2Code 插件和语言服务能稳定工作。这里涉及两个配置文件Vscode 的settings.json和 TaoToken 的接入配置。3.1 Vscode settings.json 片段在项目根目录的.vscode/settings.json里写入以下内容。compileCommands指向你实际生成的路径clangd.arguments里的--compile-commands-dir也指向同一目录{ C_Cpp.default.compileCommands: ${workspaceFolder}/Build/NT32IA32/DEBUG_VS2015x86/CompileInfo/compile_commands.json, clangd.arguments: [ --compile-commands-dir${workspaceFolder}/Build/NT32IA32/DEBUG_VS2015x86/CompileInfo, --background-index, --clang-tidyfalse, --header-insertionnever, --pch-storagememory ], edk2code.compileCommandsPath: ${workspaceFolder}/Build/NT32IA32/DEBUG_VS2015x86/CompileInfo/compile_commands.json, edk2code.indexDatabasePath: ${workspaceFolder}/Build/NT32IA32/DEBUG_VS2015x86/CompileInfo/module_report.json, edk2code.endpoint: https://taotoken.net/api, edk2code.apiKey: 你的TaoToken统一Key, edk2code.modelId: claude-sonnet-4-20250514 }这里edk2code.endpoint、edk2code.apiKey、edk2code.modelId三件套是关键。Edk2Code 在重建索引和做语义分析时会向这个 endpoint 发请求默认走公共通道容易限流。把 endpoint 指到 TaoToken 的 API 地址Key 用你在控制台生成的统一 Key模型 ID 填你开通的模型请求链路就稳定了。3.2 TaoToken 配置项与 Key 获取TaoToken 的 API 地址是https://taotoken.net/api注意这个地址不加 UTM 参数。你需要先在控制台生成一个 API Key然后把它填到上面的edk2code.apiKey字段。模型 ID 根据你开通的模型来填比如claude-sonnet-4-20250514或者gpt-4o之类。如果你用的是 Claude Code 或者 Cline 这类工具配置方式类似都是 Base URL 填https://taotoken.net/apiKey 填统一 KeyModel ID 填对应模型。Edk2Code 插件的配置项名称可能因版本略有差异如果edk2code.endpoint不生效检查一下插件版本或者在插件设置里找Endpoint、API Base之类的字段。3.3 索引重建与跳转验证配置写完后按CtrlShiftP输入EDK2: rebuild index database选择你编译后的Build目录。插件会读取module_report.json重建索引数据库。重建过程中如果 endpoint 配置正确请求会走 TaoToken 通道不会卡住。重建完成后打开一个.c文件试试Ctrl左键点EFI_STATUS或者gBS-应该能跳转到定义。如果还是跳不过去检查compile_commands.json里的file路径是不是绝对路径以及directory字段是否指向源文件所在目录。4. 验证请求与成功结果从编译到跳转的完整链路这一章把前面的步骤串起来给出一个可复现的验证流程。你按顺序执行每一步都有明确的预期结果。第一步确认补丁生效。执行build -h在输出里搜索COMPILE_INFO能看到说明build.py的choices改对了。如果看不到检查build.py是否被正确修改以及edksetup.bat是否重新执行过。第二步执行完整编译命令build -p MdeModulePkg/MdeModulePkg.dsc -a IA32 -t VS2015x86 -b DEBUG -Y COMPILE_INFO -y BuildReport.log编译结束后进入Build/NT32IA32/DEBUG_VS2015x86/CompileInfo/用ls或dir查看文件。预期看到compile_commands.json大小在几 MB 到几十 MB 之间cscope.files里是源文件路径列表module_report.json里是模块摘要。第三步用 Python 快速校验compile_commands.json的格式import json with open(Build/NT32IA32/DEBUG_VS2015x86/CompileInfo/compile_commands.json) as f: data json.load(f) print(条目数:, len(data)) print(首条:, json.dumps(data[0], indent2)[:500])预期输出条目数大于 0首条里有file、directory、command三个字段command里能看到-I或/I开头的头文件路径。第四步在 Vscode 里打开项目确认settings.json生效。按CtrlShiftP输入EDK2: rebuild index database选择Build目录。重建过程中观察 Vscode 右下角状态栏如果 endpoint 配置正确不会出现长时间卡顿。第五步跳转验证。打开MdeModulePkg/Core/Dxe/DxeMain/DxeMain.c找到gBS-或者EFI_STATUS按Ctrl左键。预期能跳转到MdePkg/Include/Uefi/UefiBaseType.h里的EFI_STATUS定义。如果跳转成功说明整条链路通了。第六步补全验证。在.c文件里输入gBS-等半秒预期弹出成员列表包含AllocatePool、FreePool、LocateProtocol等。如果补全不出来检查 clangd 是否在运行以及compile_commands.json里的头文件路径是否完整。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一章对照真实报错给出排查动作。你遇到问题时按报错关键词定位。401 Unauthorized这个报错通常出现在 Edk2Code 向 endpoint 发请求时。原因是 API Key 没填对或者 Key 过期了。排查动作打开 TaoToken 控制台确认 Key 还在有效期内然后检查settings.json里的edk2code.apiKey字段有没有多余空格。如果用的是环境变量确认变量名和插件读取的一致。local proxy failed这个报错说明插件尝试走本地代理但失败了。排查动作检查 Vscode 的http.proxy设置如果不需要代理就清空。同时确认edk2code.endpoint填的是https://taotoken.net/api不是本地地址。如果公司网络有代理确保代理配置正确但不要用任何违规的代理工具。reading choices 报错这个报错出现在build.py解析-Y参数时说明COMPILE_INFO没加进choices列表。排查动作回到build.py确认choices列表里确实有COMPILE_INFO然后重新执行edksetup.bat。如果还是报错检查是否有多个build.py副本比如BaseTools/Bin下有没有旧版本。OAuth 相关报错这个报错出现在插件尝试用 OAuth 方式认证时。排查动作确认你用的是 API Key 方式不是 OAuth。在settings.json里显式设置edk2code.apiKey不要留空。如果插件版本较老可能不支持 API Key 方式升级到最新版。compile_commands.json 为空编译成功但文件是空的。排查动作检查module.SourceFileList是否为空通常是BuildRules没匹配上源文件扩展名。确认module.BuildRules[src.Ext].CommandList[0]能取到命令如果取不到就跳过该文件。另外检查Wa.BuildDir是否正确输出目录是否可写。跳转失效但补全正常这种情况通常是module_report.json没生成或者索引没重建。排查动作确认CompileInfo目录下有module_report.json然后在 Vscode 里重新执行EDK2: rebuild index database。如果还是不行检查edk2code.indexDatabasePath是否指向正确路径。6. 把 Edk2Code 请求切到 TaoToken 统一通道的长期做法前面几章把补丁、配置、验证、排查都讲完了。这一章说一个长期做法把 Edk2Code 的请求链路固定到 TaoToken 统一通道避免每次换项目都要重新配。核心思路是把 endpoint、Key、Model ID 三件套抽出来放到一个共享的配置文件里。比如在用户目录下建一个.taotoken/edk2code.json{ endpoint: https://taotoken.net/api, apiKey: 你的统一Key, modelId: claude-sonnet-4-20250514 }然后在每个项目的.vscode/settings.json里引用这个配置。这样换项目时只需要改一处不用每个项目都填一遍。如果你同时用 Claude Code 做编码可以在 Claude Code 的配置里也填同样的 Base URL 和 Key。Claude Code 的配置路径通常在~/.claude/settings.json或者项目级的.claude/settings.jsonBase URL 填https://taotoken.net/apiKey 填统一 KeyModel ID 填对应模型。这样 Edk2Code 和 Claude Code 走同一条通道Key 统一管理限流和模型切换都好处理。对于长期做 EDK2 固件开发的同学建议把compile_commands.json的生成做成构建脚本的一部分每次编译自动产出。这样 Vscode 里的补全和跳转始终基于最新的编译数据库不会因为源码改动而失效。补丁只需要打一次后续升级 UDK2018 时注意合并冲突就行。最后提醒一点compile_commands.json文件可能很大几十 MB 是正常的。clangd 索引慢的话可以在clangd.arguments里加--background-index和--pch-storagememory把索引放到内存里加速。如果还是慢考虑只对当前模块生成编译数据库而不是整个工程。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →