ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Keil uvprojx文件自动添加:Python解析XML批量注入源文件

Keil uvprojx文件自动添加:Python解析XML批量注入源文件 1. 项目概述为什么一个“加文件”动作值得手把手教在嵌入式开发的日常里Keil µVision 是绕不开的“老伙计”。但凡做过 STM32、NXP Kinetis 或者国产 MCU 项目的工程师都经历过这样的场景新拉一个 HAL 库要手动把stm32f4xx_hal.c、stm32f4xx_hal_gpio.c、stm32f4xx_hal_rcc.c……挨个拖进工程移植一个 FatFS得一层层点开Src/目录把ff.c、ffsystem.c、diskio.c全部右键“Add to Group”更别说团队协作时同事发来一个新功能模块你得对照 README 一行行核对.c和.h文件是否漏加、路径是否写错、分组是否归类正确——一不留神编译报错undefined reference to xxx排查半小时才发现是usart.c忘了加进工程。这不是低效这是重复性认知损耗。而标题里说的“指挥AI自动添加文件到Keil工程”本质不是让AI写代码而是用 Python 做一次精准的 XML 工程文件手术。Keil 5.30 版本默认使用.uvprojx格式它本质上是一个结构清晰、标签语义明确的 XML 文件。它不像旧版.uvproj那样是二进制或加密格式而是明文可读、可解析、可修改的标准 XML。这意味着我们不需要逆向 Keil 的私有协议也不需要模拟鼠标点击——只要读懂它的 XML 结构就能像编辑 Word 文档一样把File节点精准地插入Files容器里把Group节点按需创建并挂载再把文件路径、类型C Source、Header、Assembler等属性一并写进去。整个过程不依赖 Keil 进程是否运行不触发任何 GUI 操作纯命令行驱动1 秒完成过去 3 分钟的手动操作。这个方案的核心价值远不止于“省时间”。它直接打通了嵌入式开发流程中的三个关键断点一是库管理自动化比如配合 CMake 或 Kconfig 生成的源码列表一键同步到 Keil 工程二是CI/CD 流水线集成在 GitLab CI 或 Jenkins 上每次 push 后自动更新工程文件确保所有开发者拿到的.uvprojx始终与代码树一致三是多配置工程维护一个项目同时支持 Debug/Release、LowPower/Normal 两种构建配置Python 脚本可以按条件判断只往特定Configuration下的Files中添加调试专用的trace.c而 Release 配置下则跳过。我去年带的一个电机控制项目光是 FreeRTOS CMSIS-RTOS v2 自研驱动层工程文件就超过 800 行 XML靠人工维护两周内出现过 3 次因文件遗漏导致的“功能存在但不生效”的诡异问题。自从上了这个脚本这类问题归零而且新同事入职第一天执行一条python add_files.py --src drivers/motor/ --group Motor_Driver就能获得一个完全可用的工程起点。它解决的不是“能不能做”而是“值不值得每天做十次”。2. 核心技术拆解XML 结构、Python 解析与 Keil 工程逻辑2.1 Keil .uvprojx 文件的 XML 骨架与关键节点.uvprojx不是随意堆砌的 XML它有一套 Keil 官方定义的、高度结构化的 Schema。虽然 Keil 没有公开发布 DTD 或 XSD但通过大量真实工程文件比对和官方文档片段如 ARM Compiler User Guide 中关于工程文件的描述我们可以提炼出其核心骨架。一个最小可运行的.uvprojx文件其顶层结构如下?xml version1.0 encodingUTF-8 standaloneno ? Project xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:noNamespaceSchemaLocationproject.xsd SchemaVersion2.1/SchemaVersion Header### uVision Project Data ###/Header Targets Target TargetNameMyProject/TargetName ToolsetNumber0x4/ToolsetNumber TargetOption !-- 此处省略大量编译器、链接器、调试器配置 -- /TargetOption Groups Group GroupNameStartup/GroupName Files File FileNamestartup_stm32f407xx.s/FileName FileType1/FileType FilePath.\Core\Startup\startup_stm32f407xx.s/FilePath /File /Files /Group Group GroupNameDrivers/GroupName Files File FileNamestm32f4xx_hal.c/FileName FileType1/FileType FilePath.\Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal.c/FilePath /File /Files /Group /Groups /Target /Targets /Project这里的关键节点及其业务含义必须吃透Targets一个工程可包含多个 Target目标例如MyProject_Debug和MyProject_Release。我们通常只操作第一个Target但脚本必须具备遍历能力。Groups这是文件分组的容器。Keil UI 中左侧的“Groups”列表就是由这个节点下的多个Group构成。每个Group必须有唯一的GroupName这是后续查找和插入文件的锚点。Files每个Group内部的Files是真正的文件列表容器。注意它不是一个扁平列表而是Group的子节点所有待添加的File都必须挂载在此处。File单个文件的完整描述单元。其中FileType是核心枚举值决定了 Keil 如何处理该文件1C Source File.c2Assembler Source File.s,.asm5C Header File.h8Object File.o,.obj9Library File.lib,.a10Linker Script.ld,.icf,.scf如果FileType填错比如把.h文件设为1Keil 在编译时会尝试用 C 编译器去“编译”头文件必然报错。因此脚本必须根据文件扩展名智能映射FileType不能硬编码。提示FilePath的路径是相对于.uvprojx文件所在目录的相对路径。这是最容易出错的地方。例如.uvprojx在D:\MyProject\而你想添加的文件在D:\MyProject\Drivers\stm32f4xx_hal.c那么FilePath必须写为.\Drivers\stm32f4xx_hal.c而不是绝对路径D:\MyProject\Drivers\stm32f4xx_hal.c。Keil 只认相对路径绝对路径会导致文件在 UI 中显示为“丢失”。2.2 Python XML 解析选型ElementTree vs lxml vs minidom面对 XML 操作Python 提供了三套主流方案选择哪一种直接决定脚本的健壮性和可维护性。xml.dom.minidomPython 标准库API 类似浏览器 DOM支持getElementsByTagName等方法。但它内存占用大解析大文件10MB时明显卡顿且 API 设计冗长。例如要找GroupName为Drivers的Group你需要写groups doc.getElementsByTagName(Group) for group in groups: name_node group.getElementsByTagName(GroupName)[0] if name_node.firstChild.nodeValue Drivers: files_node group.getElementsByTagName(Files)[0] break这种写法不仅啰嗦而且getElementsByTagName返回的是 NodeList索引越界异常频发不适合生产环境。lxml第三方库功能最强大支持 XPath、XSLT、Schema 验证解析速度极快。XPath 表达式//Group[GroupNameDrivers]/Files一行就能定位目标节点。但它有一个致命短板Windows 用户安装困难。pip install lxml在没有预编译 wheel 的情况下会触发 Visual Studio 编译 C 扩展对新手极不友好。而嵌入式开发主力平台恰恰是 Windows我们不能把一个“加文件”的工具变成一道“配置 Python C 编译环境”的面试题。xml.etree.ElementTree简称 ETPython 3.3 标准库轻量、稳定、跨平台。它采用树形结构API 简洁。查找Group的代码只需几行for group in root.iter(Group): name_elem group.find(GroupName) if name_elem is not None and name_elem.text Drivers: files_elem group.find(Files) if files_elem is None: # 如果 Files 不存在则创建它 files_elem ET.SubElement(group, Files) breakET.SubElement能安全地创建缺失的节点find()方法返回None而非抛异常容错性极佳。更重要的是它无需额外安装import xml.etree.ElementTree as ET即可开干。对于.uvprojx这种结构固定、规模中等通常 2MB的 XMLET 的性能和功能绰绰有余。我实测过一个包含 1200 行 XML、67 个源文件的工程ET 解析修改写回全程耗时 0.012 秒比 Keil 自身加载工程的时间还短两个数量级。因此本方案坚定选择xml.etree.ElementTree。它不是“最炫酷”的但它是“最可靠、最易部署、最贴合嵌入式工程师工作流”的。2.3 “指挥AI”的真相规则引擎而非机器学习标题里的“指挥AI”容易引发误解以为要用到 LLM大语言模型或者复杂的 AI 框架。实际上这里的“AI”是广义的“自动化智能Automated Intelligence”核心是一套基于规则的、确定性的状态机。整个脚本的逻辑流程可以抽象为一个四步状态机解析Parse用 ET 加载.uvprojx构建内存中的 Element 树。定位Locate根据用户输入的--group参数在Groups中查找GroupName匹配的Group节点。如果不存在则按需创建。生成Generate对每一个待添加的文件路径来自--src或--file提取其文件名、扩展名、相对路径并根据扩展名查表得到FileType构造一个完整的FileElement。注入Inject将新生成的FileElement追加到上一步定位到的Files节点下。最后将修改后的树写回原文件。这个过程没有任何概率、没有训练、没有预测100% 确定。它之所以“智能”在于它能理解 Keil 的 XML 语义并做出符合 Keil 期望的、精准的修改。这就像一个精通 Keil 工程语法的“文书机器人”而不是一个会“思考”的 AI。这种设计带来了两大优势一是结果可预期每一次运行只要输入相同输出的 XML 就完全一致便于版本控制和审计二是调试成本极低当出错时你可以直接打印出ET.tostring(root)查看内存中的 XML 树问题一目了然。相比之下一个基于 LLM 的方案可能今天能加.c明天就把.h当成.c处理了这种不确定性在嵌入式领域是不可接受的。3. 实操全流程从零开始编写并运行你的文件添加脚本3.1 环境准备与脚本骨架搭建在动手写代码前先确认你的 Python 环境。嵌入式工程师常用的 Python 版本是 3.8~3.11本脚本兼容所有这些版本。无需安装任何第三方包标准库足矣。首先创建一个名为keil_add_files.py的文件。我们从最简骨架开始它能接收参数并打印出来验证命令行接口是否通畅#!/usr/bin/env python3 # -*- coding: utf-8 -*- Keil uVision .uvprojx 文件自动添加源文件工具 作者一位被Keil折磨多年的嵌入式老兵 import argparse import os import sys import xml.etree.ElementTree as ET def main(): parser argparse.ArgumentParser( description向Keil uVision .uvprojx工程文件中批量添加源文件, formatter_classargparse.RawDescriptionHelpFormatter, epilog 使用示例 # 将 drivers/gpio/ 目录下所有 .c 和 .h 文件添加到 Drivers 分组 python keil_add_files.py --project myproject.uvprojx --src drivers/gpio/ --group Drivers # 添加单个汇编文件到 Startup 分组 python keil_add_files.py --project myproject.uvprojx --file startup_stm32f407xx.s --group Startup # 添加多个指定文件并指定为 Custom 分组若不存在则创建 python keil_add_files.py --project myproject.uvprojx --file custom_driver.c custom_utils.h --group Custom ) parser.add_argument(--project, -p, requiredTrue, helpKeil工程文件路径例如 myproject.uvprojx) parser.add_argument(--src, -s, help源文件所在目录路径将递归添加该目录下所有匹配的文件) parser.add_argument(--file, -f, nargs, help指定一个或多个具体文件路径) parser.add_argument(--group, -g, requiredTrue, helpKeil工程中的分组名称例如 Drivers, Startup) parser.add_argument(--dry-run, actionstore_true, help仅模拟运行不实际修改文件用于预览) args parser.parse_args() # 基础校验 if not os.path.exists(args.project): print(f错误工程文件 {args.project} 不存在。) sys.exit(1) if not (args.src or args.file): print(错误必须指定 --src 或 --file 参数。) sys.exit(1) print(f正在处理工程{args.project}) print(f目标分组{args.group}) if args.src: print(f源目录{args.src}) if args.file: print(f指定文件{, .join(args.file)}) if args.dry_run: print(【模拟模式】不会修改任何文件。) if __name__ __main__: main()将这段代码保存后在命令行中执行python keil_add_files.py -h你会看到一个格式清晰的帮助信息包含了所有参数说明和使用示例。这一步看似简单却是专业脚本的基石良好的 CLI 接口能让用户一眼看懂怎么用避免了翻文档的麻烦。epilog中的示例直接来源于我日常工作中最常遇到的三种场景覆盖了 95% 的需求。3.2 核心逻辑实现解析、定位、生成与注入现在我们向main()函数中注入真正的业务逻辑。这部分是整个脚本的“心脏”我们将分步实现。第一步解析工程文件# 解析 XML try: tree ET.parse(args.project) root tree.getroot() except ET.ParseError as e: print(f错误无法解析工程文件 {args.project}。XML 格式错误位置{e.position}) sys.exit(1) except Exception as e: print(f错误读取工程文件时发生未知错误{e}) sys.exit(1)ET.parse()会抛出ParseError其e.position属性能精确指出 XML 错误在哪一行哪一列。这对于调试被意外损坏的.uvprojx文件至关重要。我曾遇到过一次同事用 Notepad 保存时编码选成了 UTF-8-BOM导致 Keil 能打开但 Python 解析失败e.position直接定位到第一行问题秒解。第二步定位目标Group# 查找或创建目标 Group targets root.findall(.//Targets/Target) if not targets: print(错误未在工程文件中找到 Target 节点。文件格式可能不兼容。) sys.exit(1) # 我们只操作第一个 Target这是绝大多数项目的惯例 target targets[0] groups_elem target.find(Groups) if groups_elem is None: print(错误未在 Target 中找到 Groups 节点。) sys.exit(1) # 查找现有 Group target_group None for group in groups_elem.findall(Group): name_elem group.find(GroupName) if name_elem is not None and name_elem.text args.group: target_group group break # 如果没找到则创建新的 Group if target_group is None: print(f警告分组 {args.group} 不存在将创建新分组。) target_group ET.SubElement(groups_elem, Group) name_elem ET.SubElement(target_group, GroupName) name_elem.text args.group # 创建空的 Files 容器 files_elem ET.SubElement(target_group, Files) else: # 获取现有的 Files 节点 files_elem target_group.find(Files) if files_elem is None: print(f警告分组 {args.group} 存在但缺少 Files 节点将创建。) files_elem ET.SubElement(target_group, Files)这里的关键技巧是findall(.//Targets/Target)。.//是 XPath 的简写表示“从根节点开始递归查找任意深度的Targets/Target”。这比硬编码root[0][0]这样的索引安全得多因为.uvprojx的顶层结构未来可能会有微调比如增加注释或新节点索引方式会立刻崩溃而 XPath 方式则具有强大的鲁棒性。第三步收集待添加的文件列表# 收集所有待添加的文件路径 file_paths [] if args.src: if not os.path.isdir(args.src): print(f错误源目录 {args.src} 不存在或不是一个目录。) sys.exit(1) # 递归遍历目录只添加 .c, .h, .s, .asm, .ld, .icf 等常见嵌入式文件 for root_dir, dirs, files in os.walk(args.src): for file in files: if file.lower().endswith((.c, .h, .s, .asm, .ld, .icf, .scf, .a, .lib)): full_path os.path.join(root_dir, file) file_paths.append(full_path) if args.file: for f in args.file: if not os.path.exists(f): print(f错误指定文件 {f} 不存在。) sys.exit(1) file_paths.append(f) if not file_paths: print(错误未找到任何符合条件的文件。请检查 --src 目录或 --file 参数。) sys.exit(1)os.walk()是递归遍历目录的黄金标准。我们特意将扩展名列表转为小写file.lower().endswith(...)以兼容 Windows 下文件名大小写不敏感的特性避免因main.C被忽略而引发问题。第四步生成File节点并注入# FileType 映射表 FILE_TYPE_MAP { .c: 1, .h: 5, .s: 2, .asm: 2, .ld: 10, .icf: 10, .scf: 10, .a: 9, .lib: 9, .o: 8, .obj: 8 } # 开始添加文件 added_count 0 for full_path in file_paths: # 计算相对于工程文件的相对路径 rel_path os.path.relpath(full_path, os.path.dirname(args.project)) # 将反斜杠 \ 替换为正斜杠 /Keil XML 标准要求 rel_path rel_path.replace(os.sep, /) # 获取文件名和扩展名 filename os.path.basename(full_path) _, ext os.path.splitext(full_path.lower()) # 获取 FileType file_type FILE_TYPE_MAP.get(ext, 1) # 默认为 C Source # 创建 File 节点 file_elem ET.SubElement(files_elem, File) filename_elem ET.SubElement(file_elem, FileName) filename_elem.text filename filetype_elem ET.SubElement(file_elem, FileType) filetype_elem.text file_type filepath_elem ET.SubElement(file_elem, FilePath) filepath_elem.text f.{os.sep}{rel_path} # Keil 要求路径以 .\ 开头 added_count 1 print(f成功添加 {added_count} 个文件到分组 {args.group}。)os.path.relpath()是计算相对路径的唯一正确方式它能自动处理跨盘符、上级目录..等复杂情况。f.{os.sep}{rel_path}这个拼接是 Keil 的硬性要求os.sep在 Windows 上是\所以最终是.\Drivers\stm32f4xx_hal.c完美匹配 Keil 的期望。第五步写回文件# 写回文件 if not args.dry_run: try: # 使用 ET.indent() 格式化 XML使其可读Python 3.9 if hasattr(ET, indent): ET.indent(root, space , level0) tree.write(args.project, encodingutf-8, xml_declarationTrue) except Exception as e: print(f错误写入工程文件时失败{e}) sys.exit(1) print(f工程文件 {args.project} 已成功更新。) else: print(【模拟模式结束】以上操作已预览文件未被修改。)ET.indent()是 Python 3.9 引入的神器它能自动为 XML 添加缩进和换行让生成的.uvprojx和手工编辑的一样美观极大地方便了后续的 Git Diff 查看。如果你还在用 Python 3.8可以跳过这行不影响功能。3.3 实战测试与效果验证现在让我们用一个真实的例子来跑通整个流程。假设你的项目结构如下MyProject/ ├── MyProject.uvprojx ├── Core/ │ └── main.c └── Drivers/ └── gpio/ ├── gpio.c └── gpio.h你希望把Drivers/gpio/下的所有文件添加到 Keil 工程的GPIO_Driver分组中。首次运行创建分组python keil_add_files.py --project MyProject.uvprojx --src Drivers/gpio/ --group GPIO_Driver输出正在处理工程MyProject.uvprojx 目标分组GPIO_Driver 源目录Drivers/gpio/ 警告分组 GPIO_Driver 不存在将创建新分组。 成功添加 2 个文件到分组 GPIO_Driver。 工程文件 MyProject.uvprojx 已成功更新。打开 Keil 验证启动 Keil uVision打开MyProject.uvprojx。在左侧的 Project 窗口中你应该能看到一个名为GPIO_Driver的新分组点开后gpio.c和gpio.h已经整齐地排列在里面。双击gpio.c代码能正常打开证明路径无误。二次运行追加文件现在你在Drivers/gpio/下新增了一个gpio_ext.c。再次运行python keil_add_files.py --project MyProject.uvprojx --src Drivers/gpio/ --group GPIO_Driver输出正在处理工程MyProject.uvprojx 目标分组GPIO_Driver 源目录Drivers/gpio/ 成功添加 3 个文件到分组 GPIO_Driver。 工程文件 MyProject.uvprojx 已成功更新。注意这次没有“警告”因为GPIO_Driver分组已经存在。Keil 会自动识别新添加的gpio_ext.c无需重启 IDE。模拟模式预览在执行任何可能破坏工程的操作前强烈建议先用--dry-runpython keil_add_files.py --project MyProject.uvprojx --src Drivers/gpio/ --group GPIO_Driver --dry-run它会告诉你“将添加 3 个文件”让你心里有底再放心执行。这个流程我已经在 STM32F4、GD32F3、NXP S32K144 等多个平台的十几个项目中反复验证稳定可靠。它把一个原本需要 2 分钟、且极易出错的手动操作压缩到了 1 秒并且 100% 可重复。4. 常见问题与独家避坑指南那些只有踩过才知道的坑4.1 文件路径乱码与编码问题现象脚本运行成功但 Keil 打开工程后文件名显示为一堆问号?????.c或乱码。原因.uvprojx文件本身是 UTF-8 编码但某些老旧的 Keil 版本尤其是 5.20 之前在 Windows 上对 UTF-8 的 BOMByte Order Mark处理不一致。如果你用 Notepad 或 VS Code 保存.uvprojx时不小心选择了UTF-8 with BOMPython 的ET.write()会将其写为UTF-8 without BOM导致 Keil 读取时解码错乱。解决方案这是一个“预防大于治疗”的问题。在项目初始化时就统一规范.uvprojx的编码。步骤一用 VS Code 打开.uvprojx右下角查看当前编码如果是UTF-8 with BOM点击它选择Save with Encoding-UTF-8。步骤二在脚本的tree.write()中强制指定encodingutf-8并xml_declarationTrue这会生成标准的?xml version1.0 encodingutf-8?声明Keil 对此兼容性最好。终极保险在脚本开头增加一个编码检测和修复函数def ensure_utf8_no_bom(filepath): 确保文件为 UTF-8 without BOM with open(filepath, rb) as f: raw f.read(3) if raw b\xef\xbb\xbf: # BOM detected with open(filepath, r, encodingutf-8-sig) as f: content f.read() with open(filepath, w, encodingutf-8) as f: f.write(content) print(f已移除文件 {filepath} 的 UTF-8 BOM。)在main()函数开头调用ensure_utf8_no_bom(args.project)。这个函数会在写入前先检查并清除 BOM一劳永逸。4.2 “文件已存在”重复添加问题现象多次运行脚本同一个文件在 Keil 工程中出现了两次甚至三次编译时报错multiple definition of xxx。原因脚本的设计哲学是“无脑追加”它并不检查Files列表里是否已经存在同名文件。这是有意为之的权衡。因为“检查是否存在”需要遍历整个Files对于一个有上千个文件的大型工程这会带来毫秒级的延迟而嵌入式工程师对 CLI 工具的响应速度极其敏感。更重要的是在工程管理的语义上“重复添加”本身就是一个需要被发现和修正的问题。它往往意味着你的构建脚本或 CI 流程有 bug比如make clean没有清理掉中间产物或者 Git Hook 配置错误导致脚本被触发了两次。解决方案提供一个配套的“去重”工具而不是在主逻辑里增加复杂度。def deduplicate_files_in_group(tree, group_name): 删除指定分组内重复的文件基于 FileName root tree.getroot() for target in root.findall(.//Targets/Target): groups target.find(Groups) if groups is not None: for group in groups.findall(Group): name_elem group.find(GroupName) if name_elem is not None and name_elem.text group_name: files group.find(Files) if files is not None: seen_names set() # 从后往前遍历方便安全删除 for file_elem in list(files.findall(File))[::-1]: name_elem file_elem.find(FileName) if name_elem is not None and name_elem.text in seen_names: files.remove(file_elem) print(f 删除重复文件: {name_elem.text}) else: seen_names.add(name_elem.text if name_elem is not None else ) return tree你可以把这个函数封装成--dedupe参数作为脚本的一个高级功能。这样主流程保持极致简洁而高级用户需要时可以一键清理。4.3 Keil 版本兼容性与 SchemaVersion现象脚本在 Keil 5.36 上运行完美但同事用 Keil 5.27 打开修改后的工程提示“工程文件版本不兼容”。原因.uvprojx文件顶部的SchemaVersion标签。Keil 会根据这个版本号来决定如何解析文件。不同版本的 Keil 对应不同的 SchemaVersion例如Keil 5.20 ~ 5.29:SchemaVersion通常是2.0或2.1Keil 5.30:SchemaVersion通常是2.1或2.2脚本在读取时会忠实地保留原有的SchemaVersion但在写回时如果 Keil 版本较新它可能会在下次保存时自动升级这个值。这本身不是问题但如果你的团队强制要求所有成员使用同一版本 Keil就需要确保脚本不“越界”。解决方案在脚本中增加一个--schema-version参数允许用户显式指定。parser.add_argument(--schema-version, defaultNone, help强制设置 SchemaVersion例如 2.1) ... # 在写回前 if args.schema_version: schema_elem root.find(SchemaVersion) if schema_elem is not None: schema_elem.text args.schema_version这样当你知道全团队都用 Keil 5.27 时就可以加上--schema-version 2.1确保生成的文件与大家的 IDE 完全兼容。4.4 权限不足与文件锁定现象脚本报错PermissionError: [Errno 13] Permission denied或者在 Windows 上提示The process cannot access the file because it is being used by another process。原因这是 Windows 系统的经典问题。当你在 Keil uVision 中打开了一个工程Keil 会以独占方式锁定.uvprojx文件防止其他程序包括你的 Python 脚本对其进行写入。这是操作系统级别的保护机制。解决方案这是一个无法绕过的硬性限制只能靠流程规范来规避。最佳实践将此脚本定位为“工程初始化”和“CI/CD 流水线”工具而非“IDE 内联编辑器”。也就是说它应该在你关闭 Keil 之后运行或者在 Git Pull 之后、打开 Keil 之前运行。把它当作make或cmake的一部分而不是 Keil 的插件。自动化提示在脚本中加入一个简单的文件锁定检测def is_file_locked(filepath): try: with open(filepath, r) as f: return False except PermissionError: return True if is_file_locked(args.project): print(f警告工程文件 {args.project} 当前被其他程序很可能是Keil锁定。) print(请先关闭Keil uVision然后再运行此脚本。) sys.exit(1)这个检测能在第一时间给出明确的错误信息而不是让用户在一堆 traceback 中寻找PermissionError。4.5 高级技巧与 Git Hooks 深度集成这才是真正体现“自动化”威力的地方。你可以让这个脚本成为你 Git 工作流的一部分实现“代码即工程”。场景你有一个drivers/目录里面全是模块化的驱动代码。每当有人git commit -m add: new i2c sensor driver并git push到远程仓库你希望 CI 服务器能自动拉
返回列表