From d4a86fc3c47d8e5a9b4c0b8f35c04b2a9c49cc59 Mon Sep 17 00:00:00 2001 From: nxp-ran Date: Mon, 24 Aug 2026 16:00:59 +0800 Subject: [PATCH] [tools] feat: auto-select Keil/IAR target by Kconfig linker script --- .../imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py | 22 ++++- .../imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py | 20 ++++- tools/targets/iar.py | 89 +++++++++++++++++-- tools/targets/keil.py | 66 ++++++++++---- 4 files changed, 166 insertions(+), 31 deletions(-) diff --git a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py index ab1be162d20f..b30cbe3b57a0 100644 --- a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py +++ b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py @@ -61,7 +61,6 @@ 'FLEXSPI_NOR_HYPERRAM': 'MIMXRT1189xxxxx_cm33_flexspi_nor_hyperram', }[_LINKER_SCRIPT_TYPE] - if PLATFORM == 'gcc': PREFIX = 'arm-none-eabi-' CC = PREFIX + 'gcc' @@ -229,18 +228,23 @@ EXEC_PATH = EXEC_PATH + '/arm/bin/' POST_ACTION = 'ielftool --bin $TARGET rtthread.bin' -# Map from linker script type to the matching Keil target name. -_LINKER_SCRIPT_TO_KEIL_TARGET = { +# Map from linker script type to the matching Keil target / IAR configuration name. +# Keil targets and IAR configurations share the same names, so one table serves both. +_LINKER_SCRIPT_TO_PROJECT_TARGET = { 'RAM': 'rtthread_ram', 'FLEXSPI_NOR': 'rtthread_flexspi_nor', 'FLEXSPI_NOR_HYPERRAM': 'rtthread_flexspi_nor_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') + def update_keil_active_target(uvoptx_path='project.uvoptx'): """Set in project.uvoptx to match the selected linker script.""" import xml.etree.ElementTree as etree - active = _LINKER_SCRIPT_TO_KEIL_TARGET.get(_LINKER_SCRIPT_TYPE, 'rtthread_ram') + active = _get_active_project_target() if not os.path.exists(uvoptx_path): return @@ -261,7 +265,17 @@ def update_keil_active_target(uvoptx_path='project.uvoptx'): print('Keil active target set to: ' + active) +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() + def dist_handle(BSP_ROOT, dist_dir): + import sys cwd_path = os.getcwd() # sys.path.append(os.path.join(os.path.dirname(BSP_ROOT), 'tools')) diff --git a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py index 5cd0142118f8..4eb634f6c3d9 100644 --- a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py +++ b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py @@ -242,19 +242,24 @@ EXEC_PATH = EXEC_PATH + '/arm/bin/' POST_ACTION = 'ielftool --bin $TARGET rtthread.bin' -# Map from linker script type to the matching Keil target name. -_LINKER_SCRIPT_TO_KEIL_TARGET = { +# Map from linker script type to the matching Keil target / IAR configuration name. +# Keil targets and IAR configurations share the same names, so one table serves both. +_LINKER_SCRIPT_TO_PROJECT_TARGET = { 'RAM': 'rtthread_ram', 'HYPERRAM': 'rtthread_hyperram', 'FLEXSPI_NOR': 'rtthread_flexspi_nor', 'FLEXSPI_NOR_HYPERRAM': 'rtthread_flexspi_nor_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') + def update_keil_active_target(uvoptx_path='project.uvoptx'): """Set in project.uvoptx to match the selected linker script.""" import xml.etree.ElementTree as etree - active = _LINKER_SCRIPT_TO_KEIL_TARGET.get(_LINKER_SCRIPT_TYPE, 'rtthread_ram') + active = _get_active_project_target() if not os.path.exists(uvoptx_path): return @@ -275,6 +280,15 @@ def update_keil_active_target(uvoptx_path='project.uvoptx'): print('Keil active target set to: ' + active) +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() + def dist_handle(BSP_ROOT, dist_dir): import sys cwd_path = os.getcwd() diff --git a/tools/targets/iar.py b/tools/targets/iar.py index ee53ad2e0010..5f45b2ce8de0 100644 --- a/tools/targets/iar.py +++ b/tools/targets/iar.py @@ -40,14 +40,15 @@ - $WS_DIR$\%s - + $WS_DIR$\%(project)s + %(active_config)s ''' + def IARAddGroup(parent, name, files, project_path): group = SubElement(parent, 'group') group_name = SubElement(group, 'name') @@ -69,11 +70,47 @@ def IARAddGroup(parent, name, files, project_path): else: file_name.text = '$PROJ_DIR$\\' + path # ('$PROJ_DIR$\\' + path).decode(fs_encoding) -def IARWorkspace(target): - # make an workspace +def _update_iar_wsdt(wsdt_path, project_name, active_config): + """Update in the IAR session file to set the active configuration.""" + config_str = '%s/%s' % (project_name, active_config) + + if not os.path.exists(wsdt_path): + # create a minimal wsdt if it does not exist yet + os.makedirs(os.path.dirname(wsdt_path), exist_ok=True) + content = '\n\n \n \n %s\n \n \n\n' % 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: + # create the elements if missing + cfg_dict = root.find('ConfigDictionary') + if cfg_dict is None: + cfg_dict = SubElement(root, 'ConfigDictionary') + cur_cfgs = cfg_dict.find('CurrentConfigs') + if cur_cfgs is None: + cur_cfgs = SubElement(cfg_dict, 'CurrentConfigs') + proj_elem = SubElement(cur_cfgs, 'Project') + proj_elem.text = config_str + tree.write(wsdt_path, encoding='unicode', xml_declaration=True) + except Exception as e: + print('Warning: could not update %s: %s' % (wsdt_path, e)) + +def IARWorkspace(target, active_config=None): + # make an workspace, optionally setting the active configuration workspace = target.replace('.ewp', '.eww') + project_name = os.path.splitext(os.path.basename(target))[0] + active_elem = '' + if active_config: + active_elem = '\n \n %s/%s\n ' % (project_name, active_config) out = open(workspace, 'w') - xml = iar_workspace % target + xml = iar_workspace % {'project': target, 'active_config': active_elem} out.write(xml) out.close() @@ -87,6 +124,7 @@ def IARProject(env, target, script): CPPPATH = [] CPPDEFINES = env.get('CPPDEFINES', []) + LOCAL_CPPDEFINES = [] LINKFLAGS = '' CFLAGS = '' @@ -160,6 +198,13 @@ def searchLib(group): state = SubElement(option, 'state') state.text = define + 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 + if name.text == 'IlinkAdditionalLibs': for path in Libs: state = SubElement(option, 'state') @@ -173,7 +218,39 @@ def searchLib(group): out.write(etree.tostring(root, encoding='utf-8').decode()) out.close() - IARWorkspace(target) + # Determine the active configuration from the BSP via an optional board-specific hook. + 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) + + # update settings/project.wsdt to set the active configuration + 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) + + # copy template.ewd (debugger settings) and template.ewt (build settings) to project files + 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_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) def IARPath(): import rtconfig diff --git a/tools/targets/keil.py b/tools/targets/keil.py index 452f273dffd4..a765b338fe0d 100644 --- a/tools/targets/keil.py +++ b/tools/targets/keil.py @@ -231,11 +231,15 @@ def MDK45Project(env, tree, target, script): CFLAGS = '' ProjectFiles = [] - # add group - groups = tree.find('Targets/Target/Groups') + import copy + + # add groups to the first target; they will be copied to all other targets afterward + first_target = tree.find('Targets/Target') + groups = first_target.find('Groups') if groups is None: - groups = SubElement(tree.find('Targets/Target'), 'Groups') + groups = SubElement(first_target, 'Groups') groups.clear() # clean old groups + for group in script: group_tree = MDK4AddGroup(ProjectFiles, groups, group['name'], group['src'], project_path, group) @@ -284,23 +288,39 @@ def MDK45Project(env, tree, target, script): else: group_tree = MDK4AddGroupForFN(ProjectFiles, groups, group['name'], full_path, project_path) - # write include path, definitions and link flags - IncludePath = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/VariousControls/IncludePath') - IncludePath.text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in set(CPPPATH)]) + # write include path, definitions and link flags for all targets in the template + include_path_text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in set(CPPPATH)]) + define_text = ', '.join(set(CPPDEFINES)) + + for target_node in tree.findall('Targets/Target'): + # copy groups from the first target to all other targets so they share the same source file list + 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('TargetOption/TargetArmAds/Cads/VariousControls/IncludePath') + if inc is not None: + inc.text = include_path_text - Define = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/VariousControls/Define') - Define.text = ', '.join(set(CPPDEFINES)) + dfn = target_node.find('TargetOption/TargetArmAds/Cads/VariousControls/Define') + if dfn is not None: + dfn.text = define_text - if 'c99' in CXXFLAGS or 'c99' in CCFLAGS or 'c99' in CFLAGS: - uC99 = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/uC99') - uC99.text = '1' + if 'c99' in CXXFLAGS or 'c99' in CCFLAGS or 'c99' in CFLAGS: + uC99 = target_node.find('TargetOption/TargetArmAds/Cads/uC99') + if uC99 is not None: + uC99.text = '1' - if 'gnu' in CXXFLAGS or 'gnu' in CCFLAGS or 'gnu' in CFLAGS: - uGnu = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/uGnu') - uGnu.text = '1' + if 'gnu' in CXXFLAGS or 'gnu' in CCFLAGS or 'gnu' in CFLAGS: + uGnu = target_node.find('TargetOption/TargetArmAds/Cads/uGnu') + if uGnu is not None: + uGnu.text = '1' - Misc = tree.find('Targets/Target/TargetOption/TargetArmAds/LDads/Misc') - Misc.text = LINKFLAGS + misc = target_node.find('TargetOption/TargetArmAds/LDads/Misc') + if misc is not None: + misc.text = LINKFLAGS xml_indent(root) out.write(etree.tostring(root, encoding='utf-8').decode()) @@ -362,12 +382,22 @@ def MDK5Project(env, target, script): # copy uvopt file if os.path.exists('template.uvoptx'): import shutil - shutil.copy2('template.uvoptx', '{}.uvoptx'.format(os.path.splitext(target)[0])) - # build with UV4.exe + project_uvoptx = '{}.uvoptx'.format(os.path.splitext(target)[0]) + shutil.copy2('template.uvoptx', project_uvoptx) + + # Set the active target from the BSP via an optional board-specific hook. + 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: target_name = template_tree.find('Targets/Target/TargetName') print('target_name:', target_name.text) + log_file_path = 'keil.log' if os.path.exists(log_file_path): os.remove(log_file_path)