Skip to content

[tools] feat: auto-select Keil/IAR target by Kconfig linker script - #11743

Open
nxp-ran wants to merge 3 commits into
RT-Thread:masterfrom
nxp-ran:feature/imxrt1180-keil-iar-target
Open

[tools] feat: auto-select Keil/IAR target by Kconfig linker script#11743
nxp-ran wants to merge 3 commits into
RT-Thread:masterfrom
nxp-ran:feature/imxrt1180-keil-iar-target

Conversation

@nxp-ran

@nxp-ran nxp-ran commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

拉取/合并请求描述:(PR description)

[

为什么提交这份PR (why to submit this PR)

i.MX RT1180 EVK BSP 通过 Kconfig 选择不同的链接脚本(RAM / FLEXSPI_NOR / HYPERRAM / FLEXSPI_NOR_HYPERRAM),并在 Keil/IAR 工程中对应不同的 target/configuration。此前生成的工程无法自动选中与所选链接脚本匹配的 target/配置,需要手动切换。本 PR 通过在通用工具中引入"可选板级钩子",让 BSP 自行决定激活哪个 target/配置,对不定义钩子的其他芯片零影响。

你的解决方案是什么 (what is your solution)

  • 板级专属逻辑(linker script -> target 名映射)放在 BSP 的 rtconfig.py。
  • 通用工具(tools/targets/keil.py、iar.py)只通过 hasattr(rtconfig, ...) 探测并调用可选钩子,用 try/except 守卫。
  • 未定义钩子的其他板子/芯片走原有代码路径,生成结果不变,向后兼容。

tools/targets/iar.py 改动详解

1. 工作区模板占位符改造

改动前:

iar_workspace = r'''... <path>$WS_DIR$\%s</path> ...'''
xml = iar_workspace % target

改动后:

iar_workspace = r'''...
  <project>
    <path>$WS_DIR$\%(project)s</path>
  </project>%(active_config)s
  <batchBuild/>
...'''
xml = iar_workspace % {'project': target, 'active_config': active_elem}

说明:把匿名占位符 %s 改为命名占位符,新增 %(active_config)s 用于插入 <activeConfig> 段。无激活配置时 active_elem 为空串,渲染结果与旧版逐字节一致,保证对其他板子无影响。

2. IARWorkspace 增加 active_config 参数

def IARWorkspace(target, active_config=None):
    workspace = target.replace('.ewp', '.eww')
    project_name = os.path.splitext(os.path.basename(target))[0]
    active_elem = ''
    if active_config:
        active_elem = '\n  <activeConfig>\n    <name>%s/%s</name>\n  </activeConfig>' % (project_name, active_config)
    out = open(workspace, 'w')
    xml = iar_workspace % {'project': target, 'active_config': active_elem}
    out.write(xml)
    out.close()

说明:新增可选参数(默认 None,向后兼容)。有配置时在 .eww 中写入 <activeConfig><name>项目名/配置名</name></activeConfig>,声明工程默认激活哪个 configuration。

3. 新增 _update_iar_wsdt() 函数

def _update_iar_wsdt(wsdt_path, project_name, active_config):
    config_str = '%s/%s' % (project_name, active_config)
    if not os.path.exists(wsdt_path):
        # 文件不存在则创建最小化 .wsdt
        os.makedirs(os.path.dirname(wsdt_path), exist_ok=True)
        content = '...<ConfigDictionary><CurrentConfigs><Project>%s</Project>...' % config_str
        with open(wsdt_path, 'w') as f:
            f.write(content)
        return
    try:
        tree = etree.parse(wsdt_path)
        root = tree.getroot()
        proj_elem = root.find('ConfigDictionary/CurrentConfigs/Project')
        if proj_elem is not None:
            proj_elem.text = config_str          # 已有节点直接改文本
        else:
            # 缺失则逐级补建 ConfigDictionary -> CurrentConfigs -> Project
            ...
        tree.write(wsdt_path, encoding='unicode', xml_declaration=True)
    except Exception as e:
        print('Warning: could not update %s: %s' % (wsdt_path, e))

说明:.wsdt 是 IAR 的会话文件,其 <CurrentConfigs><Project> 决定 IDE 实际选中的配置。只更新 .eww 不够,需同步 .wsdt。函数分"文件不存在则新建"和"文件存在则改/补节点"两种情况,出错只警告不中断。

4. IlinkConfigDefines 处理块(新增)

if name.text == 'IlinkConfigDefines':
    # write bare symbol=value tokens from LINKFLAGS as IAR linker defines
    import re
    for token in re.findall(r'\S+', LINKFLAGS):
        state = SubElement(option, 'state')
        state.text = token

说明:这是更新 IAR 工程的 IlinkConfigDefines 选项(对应 IDE 中 Linker -> Config -> Configuration file symbol definitions)。作用是把 SCons 收集到的 LINKFLAGS 里的链接器符号定义(如 symbol=value 这类给链接脚本用的宏)用 re.findall(r'\S+', ...) 按空白拆分成一个个 token,写成 <state> 子节点注入到该 option 下,使链接脚本依赖的符号能正确传给 ilinkarm。

注意:此块不受 active_config 守卫,对所有 IAR 板子生效;但仅当模板 .ewp 中存在 IlinkConfigDefines 选项且 LINKFLAGS 非空时才写入,多数板子该项为空、行为基本不变。

5. IARProject 中的钩子调用与文件复制(新增)

# 通过可选板级钩子获取激活配置
active_config = None
try:
    import rtconfig
    if hasattr(rtconfig, 'iar_get_active_config'):
        active_config = rtconfig.iar_get_active_config()
except Exception as e:
    print('Warning: could not get IAR active config: %s' % e)

IARWorkspace(target, active_config)

# 有配置时同步 settings/<project>.wsdt
if active_config:
    wsdt_path = os.path.join('settings', os.path.splitext(os.path.basename(target))[0] + '.wsdt')
    project_name = os.path.splitext(os.path.basename(target))[0]
    _update_iar_wsdt(wsdt_path, project_name, active_config)

# 复制 template.ewd(调试器设置) / template.ewt(构建设置) 到工程文件
import shutil
ewd_template = target.replace('.ewp', '.ewd').replace('project', 'template')
ewd_target   = target.replace('.ewp', '.ewd')
if not os.path.exists(ewd_template):
    ewd_template = 'template.ewd'
if os.path.exists(ewd_template):
    shutil.copy2(ewd_template, ewd_target)
# .ewt 同理

说明:

  • 钩子部分用 hasattr + try/except 守卫,其他芯片没有 iar_get_active_configactive_config 保持 None.wsdt 更新整段跳过,行为不变。
  • .ewd/.ewt 复制:把调试器设置和构建设置从模板复制到实际工程文件,均有 os.path.exists 保护,模板不存在则安全跳过。此段不受 active_config 守卫,对所有 IAR 板子生效。

tools/targets/keil.py 改动详解

1. MDK45Project:多 Target 支持

改动前(只处理第一个 Target):

groups = tree.find('Targets/Target/Groups')
if groups is None:
    groups = SubElement(tree.find('Targets/Target'), 'Groups')
groups.clear()
...
IncludePath = tree.find('Targets/Target/.../IncludePath')
IncludePath.text = ...
Define = tree.find('Targets/Target/.../Define')
Define.text = ...

改动后(遍历所有 Target):

import copy
first_target = tree.find('Targets/Target')
groups = first_target.find('Groups')
if groups is None:
    groups = SubElement(first_target, 'Groups')
groups.clear()
...
include_path_text = ';'.join([...])
define_text = ', '.join(set(CPPDEFINES))
for target_node in tree.findall('Targets/Target'):
    # 把第一个 target 的 groups 深拷贝到其它 target,使各 target 共享同一份源文件列表
    if target_node is not first_target:
        existing_groups = target_node.find('Groups')
        if existing_groups is not None:
            target_node.remove(existing_groups)
        target_node.append(copy.deepcopy(groups))
    inc = target_node.find('.../IncludePath')
    if inc is not None:
        inc.text = include_path_text
    dfn = target_node.find('.../Define')
    if dfn is not None:
        dfn.text = define_text
    # uC99 / uGnu / Misc 同样在循环内、加 is not None 保护

说明:

  • 旧逻辑只给模板里第一个 Target 填充 groups/include/define/link flags,多 target 模板下其余 target 是空的。
  • 新逻辑先在第一个 target 构建 groups,再用 copy.deepcopy 复制到所有其它 target,并对每个 target 分别写入 IncludePath、Define、uC99、uGnu、Misc(链接选项)。
  • 所有 find 都加了 if ... is not None 空值保护,比旧版直接 .text = 更健壮。
  • 对单 target 模板(绝大多数板子):findall 只返回一个节点,target_node is first_target 恒为真,跳过 deepcopy,行为与旧版等价。

2. MDK5Project:复制 uvoptx 后调用可选钩子

改动前:

if os.path.exists('template.uvoptx'):
    import shutil
    shutil.copy2('template.uvoptx', '{}.uvoptx'.format(os.path.splitext(target)[0]))
    # build with UV4.exe
    if shutil.which('UV4.exe') is not None:
        ...

改动后:

if os.path.exists('template.uvoptx'):
    import shutil
    project_uvoptx = '{}.uvoptx'.format(os.path.splitext(target)[0])
    shutil.copy2('template.uvoptx', project_uvoptx)

    # 通过可选板级钩子设置激活 target
    try:
        import rtconfig
        if hasattr(rtconfig, 'update_keil_active_target'):
            rtconfig.update_keil_active_target(project_uvoptx)
    except Exception as e:
        print('Warning: could not set Keil active target: %s' % e)

    # build with UV4.exe
    if shutil.which('UV4.exe') is not None:
        ...

说明:复制得到 project.uvoptx 后,若 BSP 定义了 update_keil_active_target 钩子则调用它,把 .uvoptx 里匹配所选 linker script 的 target 的 <IsCurrentTarget> 置 1、其余置 0。用 hasattr + try/except 守卫,其他芯片跳过,行为不变。


bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py(cm7 同步)中的实现

1. linker script -> target/配置名映射表

# Keil target 与 IAR configuration 共用同一套名称
_LINKER_SCRIPT_TO_PROJECT_TARGET = {
    'RAM':                  'rtthread_ram',
    'FLEXSPI_NOR':          'rtthread_flexspi_nor',
    'FLEXSPI_NOR_HYPERRAM': 'rtthread_flexspi_nor_hyperram',
    # cm7 额外包含: 'HYPERRAM': 'rtthread_hyperram'
}

def _get_active_project_target():
    """Return the Keil target / IAR config name matching the selected linker script."""
    return _LINKER_SCRIPT_TO_PROJECT_TARGET.get(_LINKER_SCRIPT_TYPE, 'rtthread_ram')

2. Keil 钩子:设置激活 target

def update_keil_active_target(uvoptx_path='project.uvoptx'):
    """Set <IsCurrentTarget> in project.uvoptx to match the selected linker script."""
    import xml.etree.ElementTree as etree
    active = _get_active_project_target()
    if not os.path.exists(uvoptx_path):
        return
    tree = etree.parse(uvoptx_path)
    root = tree.getroot()
    for tgt in tree.findall('Target'):
        tname = tgt.find('TargetName')
        is_current = tgt.find('TargetOption/OPTFL/IsCurrentTarget')
        if tname is not None and is_current is not None:
            is_current.text = '1' if tname.text == active else '0'
    out = open(uvoptx_path, 'w')
    out.write('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n')
    out.write(etree.tostring(root, encoding='utf-8').decode())
    out.close()
    print('Keil active target set to: ' + active)

3. IAR 钩子:返回激活 configuration 名

def iar_get_active_config():
    """Return the IAR configuration name matching the selected linker script.

    This hook is called by tools/targets/iar.py when generating the IAR
    project so that the active configuration follows the Kconfig linker
    script selection. It is board-specific and only defined here.
    """
    return _get_active_project_target()

说明:cm7/rtconfig.py 做同样改动,仅 target 映射表多一个 HYPERRAM 条目(cm7 特有)。原 cm7 里的 _LINKER_SCRIPT_TO_KEIL_TARGET 已统一重命名为 _LINKER_SCRIPT_TO_PROJECT_TARGET,并抽出 _get_active_project_target() 供两个钩子复用。


  • .config:
    CONFIG_BSP_LINKER_SCRIPT_RAM
    CONFIG_BSP_LINKER_SCRIPT_HYPERRAM
    CONFIG_BSP_LINKER_SCRIPT_FLEXSPI_NOR
    CONFIG_BSP_LINKER_SCRIPT_FLEXSPI_NOR_HYPERRAM

新增的功能也初步测试了其他项目生成IAR和KEIL工程的兼容性,包括bsp\nxp\imx\imxrt\imxrt1060-nxp-evk, bsp\nxp\mcx\mcxn\frdm-mcxn947 和 bsp\stm32\stm32h743-st-nucleo

]

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 代码是高质量的 Code in this PR is of high quality
  • 已经使用clang-format 源码格式化工具确保格式符合RT-Thread代码规范 This PR has been formatted with clang-format and complies with RT-Thread code specification
  • 如果是新增bsp, 已经添加ci检查到.github/ALL_BSP_COMPILE.json 详细请参考链接BSP自查

@nxp-ran
nxp-ran requested a review from Rbb666 as a code owner August 24, 2026 08:41
@github-actions

Copy link
Copy Markdown

👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread!

为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。
To ensure your code complies with RT-Thread's coding style, please run the code formatting workflow by following the steps below (If the formatting of CI fails to run).


🛠 操作步骤 | Steps

  1. 前往 Actions 页面 | Go to the Actions page
    点击进入工作流 → | Click to open workflow →

  2. 点击 Run workflow | Click Run workflow

  • Use workflow from 保持默认分支(通常为 master
    Keep the default branch (usually master) in Use workflow from
  • branch 输入框填写 PR 分支 feature/imxrt1180-keil-iar-target
    Enter PR branch feature/imxrt1180-keil-iar-target in the branch field
  • 设置需排除的文件/目录(目录请以"/"结尾)
    Set files/directories to exclude (directories should end with "/")
  1. 等待工作流完成 | Wait for the workflow to complete
    格式化后的代码将作为独立提交推送至你的分支。
    The formatting changes will be pushed to your branch as a separate commit.

完成后,提交将自动更新至 feature/imxrt1180-keil-iar-target 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the feature/imxrt1180-keil-iar-target branch automatically, and the related Pull Request will be updated.

如有问题欢迎联系我们,再次感谢您的贡献!💐
If you have any questions, feel free to reach out. Thanks again for your contribution!

@github-actions github-actions Bot added BSP: NXP Code related with NXP BSP tools labels Aug 24, 2026
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

at32f415-start

  • ROM: .text +32 B (+0.0%, 65,400 B / 262,144 B, total: 25% used)

esp32-c3

  • iram0_2_seg: .flash.text +16 B (+0.0%, 229,594 B / 8,388,576 B, total: 3% used)

gd32105r-start

  • CODE: .text +32 B (+0.0%, 69,316 B / 262,144 B, total: 26% used)

hc32f334

  • FLASH: .text +32 B (+0.0%, 99,528 B / 131,072 B, total: 76% used)

hpmicro-hpm5301evklite

  • XPI0: .text -24 B (-0.0%, 124,968 B / 1,048,576 B, total: 12% used)

infineon-psoc6

  • flash: .text +32 B (+0.0%, 116,172 B / 262,144 B, total: 44% used)

k230

  • SRAM: .eh_frame +1,120 B, .sbss +8 B, .text +4,272 B (+0.5%, 1,128,403 B / 268,300,288 B, total: 0% used)

loongson-ls1cdev

  • Code: .text +56 B (+0.0%, 363,304 B)

nuvoton-m487

  • CODE: .text +92 B (+0.0%, 356,024 B / 524,288 B, total: 68% used)

qemu-virt64-aarch64

  • Code: .eh_frame +704 B, .text +6,208 B (+0.6%, 1,077,124 B)

raspberry-pico-rp2040

  • FLASH: .text +32 B (+0.0%, 114,136 B / 2,097,152 B, total: 5% used)

renesas-ra2l1

  • FLASH: .text -20 B (-0.0%, 98,256 B / 262,144 B, total: 37% used)

simulator

  • Code: .rela.dyn +264 B, .rodata +256 B, .text +216 B (+0.0%, 2,673,425 B)
  • Data: .data +256 B (+0.0%, 1,609,732 B)

stm32f407-rt-spark

  • CODE: .text +32 B (+0.0%, 85,796 B / 1,048,576 B, total: 8% used)

stm32l475-atk-pandora-llvm

  • ROM: .text +32 B (+0.0%, 83,460 B / 524,288 B, total: 16% used)

wch-ch32v208w-r0

Comment thread tools/targets/iar.py Outdated
Comment on lines +241 to +253
ewd_template = target.replace('.ewp', '.ewd').replace('project', 'template')
ewd_target = target.replace('.ewp', '.ewd')
if not os.path.exists(ewd_template):
ewd_template = 'template.ewd'
if os.path.exists(ewd_template):
shutil.copy2(ewd_template, ewd_target)

ewt_template = target.replace('.ewp', '.ewt').replace('project', 'template')
ewt_target = target.replace('.ewp', '.ewt')
if not os.path.exists(ewt_template):
ewt_template = 'template.ewt'
if os.path.exists(ewt_template):
shutil.copy2(ewt_template, ewt_target)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增逻辑从输出工程名推导模板路径。使用 --project-name=foo 时,第一次生成 foo.ewd,第二次会执行同文件复制并触发 SameFileError。模板源应固定解析为目标目录下的 template.ewd/template.ewt。

Comment thread tools/targets/keil.py Outdated
Comment on lines 383 to 406

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

钩子虽然正确更新了 project.uvoptx,但检测到 UV4.exe 后,构建逻辑仍固定读取模板中的第一个 Target。选择 FLEXSPI_NOR 时活动目标是 rtthread_flexspi_nor,实际传给 UV4.exe -t 的却仍是 rtthread_ram,可能生成错误镜像。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BSP: NXP Code related with NXP BSP tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants