keil工程 适配VSCODE clangd插件跳转 生成.clangd文件跳转脚本

#!/usr/bin/env python3
"""
将 Keil MDK 工程 (.uvprojx) 转换为 clangd 配置文件 (.clangd)。

用法:
    python3 keil2clangd.py                          # 默认搜索当前目录
    python3 keil2clangd.py /path/to/project.uvprojx  # 指定工程文件
    python3 keil2clangd.py -o /path/to/.clangd       # 指定输出路径

生成内容:
    - target: arm-none-eabi
    - std: c11
    - 所有 <IncludePath> → -I 路径 (自动过滤不存在目录)
    - 所有 <Define> → -D 宏定义
    - Keil/ARMCC 兼容宏 (__packed, __weak, __irq, __inline 等)
"""

import re
import os
import sys
import argparse
from pathlib import Path


# Keil/ARMCC 特有关键字 → clang/GCC 兼容映射
ARMCC_COMPAT_DEFINES = {
    "__CC_ARM": "",
    "__GNUC__": "",
    "__weak": "__attribute__((weak))",
    "__packed": "__attribute__((packed))",
    "__irq": "",
    "__inline": "inline",
    "__align": "",
    "__at": "",
    "__ramfunc": "",
}


def parse_uvprojx(uvprojx_path: str) -> tuple[set[str], set[str]]:
    """解析 Keil uvprojx 文件,返回 (includes, defines)"""
    with open(uvprojx_path, "r", encoding="utf-8", errors="ignore") as f:
        content = f.read()

    project_dir = os.path.dirname(os.path.abspath(uvprojx_path))

    # 提取 IncludePath
    includes = set()
    for m in re.finditer(r"<IncludePath>([^<]+)", content):
        for p in m.group(1).split(";"):
            p = p.strip()
            if not p:
                continue
            abs_p = os.path.normpath(os.path.join(project_dir, p.replace("\\", "/")))
            if os.path.isdir(abs_p):
                includes.add(abs_p)

    # 提取 Define
    defines = set()
    for m in re.finditer(r"<Define>([^<]+)", content):
        for d in m.group(1).split(","):
            d = d.strip()
            if d:
                defines.add(d)

    return includes, defines


def generate_clangd(includes: set[str], defines: set[str], output_path: str):
    """生成 .clangd YAML 文件"""
    lines = []
    lines.append("CompileFlags:")
    lines.append("  Add:")

    # 编译目标
    lines.append("    - -std=c11")
    lines.append("    - -fno-ms-compatibility")

    # Keil 原始宏定义
    for d in sorted(defines):
        lines.append(f"    - -D{d}")

    # ARMCC 兼容宏
    for macro, gcc_val in ARMCC_COMPAT_DEFINES.items():
        if gcc_val:
            lines.append(f"    - -D{macro}={gcc_val}")
        else:
            lines.append(f"    - -D{macro}")

    # Include 路径
    for p in sorted(includes):
        lines.append(f"    - -I{p}")

    content = "\n".join(lines) + "\n"

    with open(output_path, "w") as f:
        f.write(content)

    return content


def find_all_uvprojx(search_dir: str) -> list[str]:
    """搜索目录下所有 .uvprojx 文件"""
    results = []
    for root, dirs, files in os.walk(search_dir):
        for f in files:
            if f.endswith(".uvprojx"):
                results.append(os.path.join(root, f))
    return results


def pick_uvprojx(uvprojx_list: list[str]) -> str | None:
    """多个工程文件时,优先选择文件名含 'app' 的(大小写不敏感)"""
    if not uvprojx_list:
        return None
    if len(uvprojx_list) == 1:
        return uvprojx_list[0]

    # 优先匹配文件名含 "app" 的
    app_matches = [p for p in uvprojx_list
                   if "app" in os.path.basename(p).lower()]
    if app_matches:
        return app_matches[0]

    # 没有 "app" 匹配则返回第一个
    return uvprojx_list[0]


def main():
    parser = argparse.ArgumentParser(
        description="Keil uvprojx → clangd .clangd 转换器",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  %(prog)s                                          # 自动搜索 uvprojx,输出到其所在目录的上级
  %(prog)s /work/project.uvprojx                    # 指定 uvprojx
  %(prog)s project.uvprojx -o /work/mcu/.clangd      # 指定输出
        """,
    )
    parser.add_argument(
        "uvprojx", nargs="?", default=None,
        help="Keil 工程文件路径,不指定则自动搜索当前目录"
    )
    parser.add_argument(
        "-o", "--output", default=None,
        help="输出 .clangd 路径,默认输出到 uvprojx 所在目录向上两级"
    )
    args = parser.parse_args()

    # 查找 uvprojx
    uvprojx_path = args.uvprojx
    if uvprojx_path is None:
        uvprojx_list = find_all_uvprojx(os.getcwd())
        if not uvprojx_list:
            print("错误: 未找到 .uvprojx 文件,请指定路径", file=sys.stderr)
            sys.exit(1)
        uvprojx_path = pick_uvprojx(uvprojx_list)
        if len(uvprojx_list) > 1:
            print(f"找到 {len(uvprojx_list)} 个工程文件,默认选择: {os.path.basename(uvprojx_path)}")
            for p in uvprojx_list:
                mark = " ← 选中" if p == uvprojx_path else ""
                print(f"  {p}{mark}")
        else:
            print(f"自动找到: {uvprojx_path}")

    uvprojx_path = os.path.abspath(uvprojx_path)
    if not os.path.isfile(uvprojx_path):
        print(f"错误: 文件不存在 {uvprojx_path}", file=sys.stderr)
        sys.exit(1)

    # 确定输出路径: uvprojx 向上两级 (project/mdk/ → mcu/)
    if args.output:
        output_path = os.path.abspath(args.output)
    else:
        # project/mdk/uvprojx → 向上两级 → mcu/
        output_path = os.path.join(
            os.path.dirname(os.path.dirname(os.path.dirname(uvprojx_path))),
            ".clangd"
        )

    # 解析
    includes, defines = parse_uvprojx(uvprojx_path)

    # 生成
    generate_clangd(includes, defines, output_path)

    print(f"输入:   {uvprojx_path}")
    print(f"输出:   {output_path}")
    print(f"Include: {len(includes)} 个路径")
    print(f"Define:  {len(defines)} 个宏 (+ {len(ARMCC_COMPAT_DEFINES)} 个兼容宏)")
    print("完成!")


if __name__ == "__main__":
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值