keil工程批量编译构建Python脚本教程

keil工程批量编译构建Python脚本教程

摘要

背景:因迪文彩屏软件开发项目使用动态加载框架,导致有时候更改主子程序共用数据体时,需要重新构建所有keil4工程(21个),耗时耗力且无聊,于是尝试通过脚本实现自动化处理。
功能:工作目录下的所有keil工程执行编译构建,输出执行报告,提供查询结果与构建日志功能(build_report.html)
更新日志:htsfw241226
1、完善脚本,增加将编译生成后的目标文件汇总到build_results文件夹中,脚本执行窗口打印简洁的执行报告,并等待用户确认后再退出
脚本执行窗口

在这里插入图片描述

脚本源码

使用注意:
1.安装好Python环境
2.需要将UVISION_PATH变量修改成本机上的UV4.exe所在的路径
3.脚本放到工作目录下,使用管理员身份 执行,等待执行完成后可在工作目录下打开build_report.html文件查询执行结果

import os
import subprocess
import glob
import html
import shutil
import time

# Keil uVision 工具路径,根据你的安装路径修改
UVISION_PATH = r"D:\Keil_C51v952\UV4\UV4.exe"  # 或者 UV5.exe

# 初始化计数器
total_projects_num = 0
successful_builds_num = 0

# 工作目录,放置所有 .uvproj 或 .uvprojx 文件的路径,各工程名不要重名
# WORKING_DIR = r"E:\项目\967 迪文变频单系统彩屏软件V1.4升版\DW_NewColorWireCtrl\SoftwareProject\autoLoad-lib-V1"
# 提示用户输入工作目录
# WORKING_DIR = input("请输入Keil工程工作区:")
# 当前脚本所在工作区路径
WORKING_DIR = os.getcwd()

# 构建日志目录
LOG_DIR = os.path.join(WORKING_DIR, "build_logs")
os.makedirs(LOG_DIR, exist_ok=True)

# HTML 输出文件
OUTPUT_HTML = os.path.join(WORKING_DIR, "build_report.html")


def build_project(project_path):
    """调用 Keil 命令行工具构建指定项目"""
    project_name = os.path.basename(project_path)
    log_file = os.path.join(LOG_DIR, f"{project_name}.log")
    command = [UVISION_PATH, "-b", project_path, "-o", log_file]

    global total_projects_num # 全局变量
    total_projects_num += 1 # 工程计数

    # 执行项目构建命令,获取构建结果并处理
    print(f"Building project: {project_name}")
    try:
        result = subprocess.run(command, capture_output=True, text=True)
        # print(f"return code {result.returncode}")
        # 构建指令返回值,0-无错误与警告,1-有警告,2-有错误 为测试推测 htsfw241213
        # 读取构建日志文件,通过匹配成功相关字段来判断是否成功
        if os.path.exists(log_file):
            # 读取日志内容
            with open(log_file, "r", encoding="utf-8") as f:
                log_content = f.read()
            if "Target not created" in log_content:
                build_status = "FAILURE"
            else:
                build_status = "SUCCESS"
                global successful_builds_num
                successful_builds_num += 1  # 成功构建计数

            print(f"build_status = {build_status}")
    #    except subprocess.CalledProcessError as e: 无法捕捉非零返回值的异常
    #        print(f"Build failed for {project_name} with return code {e.returncode}")
    #        print("stdout:",e.stdout)
    #        print("stderr:",e.stderr)

    #        input("Press Enter to continue...")

    except Exception as e:
        build_status = "ERROR"
        log_file = None
        print(f"Error building project {project_name}: {e}")

    return build_status, log_file

def generate_html_report(projects, results):
    """生成包含构建结果的 HTML 报告"""
    html_lines = [
        "<!DOCTYPE html>",
        "<html>",
        "<head>",
        "<title>Keil Build Report</title>",
        "<style>",
        "body { font-family: Arial, sans-serif; }",
        "table { border-collapse: collapse; width: 100%; }",
        "th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }",
        "th { background-color: #f2f2f2; }",
        ".success { color: green; }",
        ".failure { color: red; }",
        ".error { color: orange; }",
        "</style>",
        "</head>",
        "<body>",
        "<h1>Keil Build Report</h1>",
        "<table>",
        "<tr><th>Project</th><th>Status</th><th>Log File</th></tr>",
    ]

    for project, (status, log_file) in zip(projects, results):
        project_name = os.path.basename(project)
        status_class = status.lower()
        log_link = f'<a href="{html.escape(log_file)}">View Log</a>' if log_file else "N/A"
        html_lines.append(
            f"<tr><td>{html.escape(project_name)}</td><td class='{status_class}'>{status}</td><td>{log_link}</td></tr>"
        )

    html_lines.extend([
        "</table>",
        "</body>",
        "</html>",
    ])

    with open(OUTPUT_HTML, "w", encoding="utf-8") as f:
        f.write("\n".join(html_lines))
    print(f"HTML report generated: {OUTPUT_HTML}")

# 汇总构建结果 htsfw241226
def collect_build_results():
    target_extensions = ['.lib', '.bin'] # 增加目标文件扩展名: ,'.axf'
    # 存放构建结果的目录名称
    build_results_dir_name = "build_results"
    # 使用 os.path.join 构建完整的目录路径
    build_results_dir = os.path.join(WORKING_DIR, build_results_dir_name)
    # 保证是空白目标目录,否则可能执行异常 htsfw241226
    if os.path.exists(build_results_dir):
        shutil.rmtree(build_results_dir)
    os.mkdir(build_results_dir)

    # 在当前工作区下查找所有目标文件
    # os.walk 获取所有文件路径(返回:目录,子目录,文件名)
    for filePath, _, files in os.walk(WORKING_DIR):
        for file in files:
            if any(file.endswith(ext) for ext in target_extensions):
                # 找到符合要求的文件,构建源文件路径和目标文件路径
                sourceFile = os.path.join(filePath, file)
                targetFile = os.path.join(build_results_dir, file)
                # 将源文件复制到汇总文件夹
                shutil.copy2(sourceFile, targetFile)

    print(f"Target files copied to {build_results_dir}")

def main():
    # 递归搜索指定目录及其子目录下所有以.uvproj(keil4,5 旧版本) 或.uvprojx(Keil MDK-ARM 新版本)为扩展名的文件
    projects = glob.glob(os.path.join(WORKING_DIR, "**","*.uvproj")) + glob.glob(os.path.join(WORKING_DIR, "**","*.uvprojx"))

    if not projects:
        print("No Keil projects found!")
        return

    results = []
    for project in projects:
        status, log_file = build_project(project)
        results.append((status, log_file))

    generate_html_report(projects, results)

    # 汇总构建结果
    collect_build_results()
    print(f"Total projects: {total_projects_num}")
    print(f"Successful builds: {successful_builds_num}")

    # 脚本执行完毕后等待用户确认
    input("执行完成,按回车键退出...")

if __name__ == "__main__":
    main()

小结

该脚本是由ChatGPT-4o生成,优化工程构建命令是否执行成功的判断逻辑。不得不感叹现在AI的强大,可以让我这个不懂Python脚本的嵌入式开发人员快速构建出可以帮助提高工作效率的自动化任务脚本,并提供了网页版的简洁表格UI来展示构建报告。或许不久的将来只需要少部分的人就可以完成人类社会大部分的生产工作吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值