From d4a86fc3c47d8e5a9b4c0b8f35c04b2a9c49cc59 Mon Sep 17 00:00:00 2001 From: nxp-ran Date: Mon, 24 Aug 2026 16:00:59 +0800 Subject: [PATCH 1/5] [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) From 410951ed369b00edfab21c0271333760c3b35d92 Mon Sep 17 00:00:00 2001 From: nxp-ran Date: Tue, 1 Sep 2026 11:20:05 +0800 Subject: [PATCH 2/5] [tools/iar] fix SameFileError and clarify active config update --- tools/targets/iar.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tools/targets/iar.py b/tools/targets/iar.py index 5f45b2ce8de0..9a7b3fb09aa2 100644 --- a/tools/targets/iar.py +++ b/tools/targets/iar.py @@ -236,20 +236,27 @@ def searchLib(group): 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 + # The IAR IDE keeps the workspace/session state in memory and writes it + # back on close, which can overwrite the active configuration we just + # generated. Remind the user to close the workspace before regenerating. + print('IAR active configuration set to: %s' % active_config) + print('Note: if the IAR workspace (.eww) is currently open, close it ' + 'before regenerating the project, otherwise the active target ' + 'will not be updated.') + + # copy template.ewd (debugger settings) and template.ewt (build settings) to project files. + # The template sources are always template.ewd / template.ewt in the project directory; + # do not derive them from the output project name, otherwise a custom --project-name + # would make source and destination identical and trigger shutil.SameFileError. import shutil - ewd_template = target.replace('.ewp', '.ewd').replace('project', 'template') + ewd_template = os.path.join(project_path, 'template.ewd') ewd_target = target.replace('.ewp', '.ewd') - if not os.path.exists(ewd_template): - ewd_template = 'template.ewd' - if os.path.exists(ewd_template): + if os.path.exists(ewd_template) and os.path.abspath(ewd_template) != os.path.abspath(ewd_target): shutil.copy2(ewd_template, ewd_target) - ewt_template = target.replace('.ewp', '.ewt').replace('project', 'template') + ewt_template = os.path.join(project_path, 'template.ewt') ewt_target = target.replace('.ewp', '.ewt') - if not os.path.exists(ewt_template): - ewt_template = 'template.ewt' - if os.path.exists(ewt_template): + if os.path.exists(ewt_template) and os.path.abspath(ewt_template) != os.path.abspath(ewt_target): shutil.copy2(ewt_template, ewt_target) def IARPath(): From 0268caf2efab51cab3f76429248618c22c7d44e2 Mon Sep 17 00:00:00 2001 From: nxp-ran Date: Tue, 1 Sep 2026 11:21:09 +0800 Subject: [PATCH 3/5] [tools/keil] build the selected active target with the real project name --- .../imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py | 8 +++++++- .../imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py | 7 ++++++- tools/targets/keil.py | 17 +++++++++++++---- 3 files changed, 26 insertions(+), 6 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 b30cbe3b57a0..d38365c0d1e6 100644 --- a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py +++ b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/rtconfig.py @@ -247,11 +247,12 @@ def update_keil_active_target(uvoptx_path='project.uvoptx'): active = _get_active_project_target() if not os.path.exists(uvoptx_path): - return + return active 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') @@ -265,7 +266,12 @@ def update_keil_active_target(uvoptx_path='project.uvoptx'): print('Keil active target set to: ' + active) + # Return the active target name so the caller (tools/targets/keil.py) can + # build the matching target with UV4.exe instead of the template's first one. + return 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 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 4eb634f6c3d9..d27c92f50d43 100644 --- a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py +++ b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/rtconfig.py @@ -262,7 +262,7 @@ def update_keil_active_target(uvoptx_path='project.uvoptx'): active = _get_active_project_target() if not os.path.exists(uvoptx_path): - return + return active tree = etree.parse(uvoptx_path) root = tree.getroot() @@ -280,6 +280,11 @@ def update_keil_active_target(uvoptx_path='project.uvoptx'): print('Keil active target set to: ' + active) + # Return the active target name so the caller (tools/targets/keil.py) can + # build the matching target with UV4.exe instead of the template's first one. + return active + + def iar_get_active_config(): """Return the IAR configuration name matching the selected linker script. diff --git a/tools/targets/keil.py b/tools/targets/keil.py index a765b338fe0d..823146d29a54 100644 --- a/tools/targets/keil.py +++ b/tools/targets/keil.py @@ -386,24 +386,33 @@ def MDK5Project(env, target, script): shutil.copy2('template.uvoptx', project_uvoptx) # Set the active target from the BSP via an optional board-specific hook. + # The hook returns the active target name so we build the matching target. + active_target_name = None try: import rtconfig if hasattr(rtconfig, 'update_keil_active_target'): - rtconfig.update_keil_active_target(project_uvoptx) + active_target_name = 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) + # Prefer the active target reported by the BSP hook; fall back to the + # template's first target only when no hook is available. + if not active_target_name: + target_name = template_tree.find('Targets/Target/TargetName') + active_target_name = target_name.text + print('target_name:', active_target_name) + + # Use the actual generated project file name (honors --project-name). + uvprojx_name = os.path.basename(target) log_file_path = 'keil.log' if os.path.exists(log_file_path): os.remove(log_file_path) log_thread = threading.Thread(target=monitor_log_file, args=(log_file_path,)) log_thread.start() - cmd = 'UV4.exe -b project.uvprojx -q -j0 -t '+ target_name.text +' -o '+log_file_path + cmd = 'UV4.exe -b '+ uvprojx_name +' -q -j0 -t '+ active_target_name +' -o '+log_file_path print('Start to build keil project') print(cmd) os.system(cmd) From 8931ce7c484fb06df36c3c629b542d3aa3ca4e04 Mon Sep 17 00:00:00 2001 From: nxp-ran Date: Fri, 13 Nov 2026 14:31:40 +0800 Subject: [PATCH 4/5] [tools] Fix Keil targets and IAR linker options Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../imxrt1180-nxp-evk/cm33/board/SConscript | 4 +- .../imxrt1180-nxp-evk/cm7/board/SConscript | 4 +- tools/targets/iar.py | 105 ++++++++++++------ tools/targets/keil.py | 16 ++- tools/testcases/test_ide_targets.py | 104 +++++++++++++++++ 5 files changed, 194 insertions(+), 39 deletions(-) create mode 100644 tools/testcases/test_ide_targets.py diff --git a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/board/SConscript b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/board/SConscript index b5ab58e802d3..3b9d91d0d1a9 100644 --- a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/board/SConscript +++ b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/board/SConscript @@ -58,8 +58,8 @@ if GetDepend(['BSP_USE_MULTICORE']): LINKFLAGS += ' --predefine="-D__use_shmem__=1"' LINKFLAGS += ' --predefine="-D__multicore__=1"' if rtconfig.PLATFORM in ['iccarm']: - LINKFLAGS += ' __use_shmem__=1' - LINKFLAGS += ' __multicore__=1' + LINKFLAGS += ' --config_def __use_shmem__=1' + LINKFLAGS += ' --config_def __multicore__=1' if GetDepend(['BSP_USING_ETH']): CPPDEFINES += ['SDK_NETC_USED=1', 'FSL_ETH_ENABLE_CACHE_CONTROL=1'] diff --git a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/board/SConscript b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/board/SConscript index f9fa157f1ada..282a90491d97 100644 --- a/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/board/SConscript +++ b/bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm7/board/SConscript @@ -61,8 +61,8 @@ if GetDepend(['BSP_USE_MULTICORE']): LINKFLAGS += ' --predefine="-D__use_shmem__=1"' LINKFLAGS += ' --predefine="-D__multicore__=1"' if rtconfig.PLATFORM in ['iccarm']: - LINKFLAGS += ' __use_shmem__=1' - LINKFLAGS += ' __multicore__=1' + LINKFLAGS += ' --config_def __use_shmem__=1' + LINKFLAGS += ' --config_def __multicore__=1' if GetDepend(['BSP_USING_ETH']): CPPDEFINES += ['SDK_NETC_USED=1', 'FSL_ETH_ENABLE_CACHE_CONTROL=1'] diff --git a/tools/targets/iar.py b/tools/targets/iar.py index 9a7b3fb09aa2..9170b038087b 100644 --- a/tools/targets/iar.py +++ b/tools/targets/iar.py @@ -23,6 +23,7 @@ # import os +import re import sys import string import utils @@ -75,32 +76,54 @@ 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): - # 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.ElementTree(etree.Element('Workspace')) + else: 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 + + root = tree.getroot() + 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 = cur_cfgs.find('Project') + if proj_elem is None: + proj_elem = SubElement(cur_cfgs, 'Project') + proj_elem.text = config_str + tree.write(wsdt_path, encoding='utf-8', xml_declaration=True) + +def _split_iar_linkflags(linkflags): + tokens = re.findall(r'(?:[^\s"]+|"[^"]*")+', linkflags) + if re.sub(r'\s+', '', ''.join(tokens)) != re.sub(r'\s+', '', linkflags): + raise ValueError('Invalid quoting in IAR linker flags: %s' % linkflags) + + defines = [] + extra = [] + index = 0 + while index < len(tokens): + token = tokens[index] + if token == '--config_def': + index += 1 + if index == len(tokens): + raise ValueError('Missing value for --config_def') + define = tokens[index] + elif token.startswith('--config_def='): + define = token[len('--config_def='):] 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)) + extra.append(token) + index += 1 + continue + + if define.startswith('"') and define.endswith('"'): + define = define[1:-1] + if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*=.+', define): + raise ValueError('Invalid IAR configuration definition: %s' % define) + defines.append(define) + index += 1 + + return defines, ' '.join(extra) def IARWorkspace(target, active_config=None): # make an workspace, optionally setting the active configuration @@ -120,8 +143,6 @@ def IARProject(env, target, script): tree = etree.parse('template.ewp') root = tree.getroot() - out = open(target, 'w') - CPPPATH = [] CPPDEFINES = env.get('CPPDEFINES', []) @@ -156,7 +177,7 @@ def searchLib(group): # get each group's link flags if 'LINKFLAGS' in group and group['LINKFLAGS']: - LINKFLAGS += group['LINKFLAGS'] + LINKFLAGS += ' ' + group['LINKFLAGS'] if 'LIBS' in group and group['LIBS']: for item in group['LIBS']: @@ -175,6 +196,16 @@ def searchLib(group): paths[inc] = None # 使用 dict 去重并保持插入顺序 paths = list(paths.keys()) + config_defines, extra_linkflags = _split_iar_linkflags(LINKFLAGS) + + if config_defines or extra_linkflags: + for config in tree.findall('configuration'): + names = [option.findtext('name') for option in config.findall('settings/data/option')] + if config_defines and 'IlinkConfigDefines' not in names: + raise ValueError('IAR configuration has no IlinkConfigDefines option') + if extra_linkflags and ('IlinkUseExtraOptions' not in names or 'IlinkExtraOptions' not in names): + raise ValueError('IAR configuration has no linker extra options') + # setting options options = tree.findall('configuration/settings/data/option') for option in options: @@ -199,11 +230,21 @@ def searchLib(group): 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): + for define in config_defines: + state = SubElement(option, 'state') + state.text = define + + if name.text == 'IlinkUseExtraOptions' and extra_linkflags: + state = option.find('state') + if state is None: + state = SubElement(option, 'state') + state.text = '1' + + if name.text == 'IlinkExtraOptions' and extra_linkflags: + state = option.find('state') + if state is None: state = SubElement(option, 'state') - state.text = token + state.text = ' '.join(filter(None, [state.text, extra_linkflags])) if name.text == 'IlinkAdditionalLibs': for path in Libs: @@ -215,8 +256,8 @@ def searchLib(group): state.text = path xml_indent(root) - out.write(etree.tostring(root, encoding='utf-8').decode()) - out.close() + with open(target, 'w') as out: + out.write(etree.tostring(root, encoding='utf-8').decode()) # Determine the active configuration from the BSP via an optional board-specific hook. active_config = None diff --git a/tools/targets/keil.py b/tools/targets/keil.py index 88685a7bc134..033fc988b820 100644 --- a/tools/targets/keil.py +++ b/tools/targets/keil.py @@ -288,11 +288,21 @@ 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') # Keep the same include path precedence as the SCons build environment. paths = [_make_path_relative(project_path, os.path.normpath(i)) for i in CPPPATH] - IncludePath.text = ';'.join(dict.fromkeys(paths)) + include_path_text = ';'.join(dict.fromkeys(paths)) + define_text = ', '.join(set(CPPDEFINES)) + + for target_node in tree.findall('Targets/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('TargetOption/TargetArmAds/Cads/VariousControls/IncludePath') + if inc is not None: + inc.text = include_path_text dfn = target_node.find('TargetOption/TargetArmAds/Cads/VariousControls/Define') if dfn is not None: diff --git a/tools/testcases/test_ide_targets.py b/tools/testcases/test_ide_targets.py new file mode 100644 index 000000000000..deedcafa679d --- /dev/null +++ b/tools/testcases/test_ide_targets.py @@ -0,0 +1,104 @@ +import importlib.util +import os +import shutil +import sys +import tempfile +import types +import unittest +import xml.etree.ElementTree as etree +from pathlib import Path +from unittest import mock + + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT / 'tools')) + + +def load_target(name): + spec = importlib.util.spec_from_file_location(name, ROOT / 'tools' / 'targets' / (name + '.py')) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class IDETargetTests(unittest.TestCase): + def test_keil_targets_preserve_include_order(self): + keil = load_target('keil') + target_xml = ''' + 00 + + + ''' + tree = etree.ElementTree(etree.fromstring('' + target_xml * 2 + '')) + with tempfile.TemporaryDirectory() as temp: + includes = [os.path.join(temp, 'first'), os.path.join(temp, 'second')] + source = types.SimpleNamespace(name='main.c', abspath=os.path.join(temp, 'main.c')) + node = types.SimpleNamespace(rfile=lambda: source) + group = {'name': 'test', 'src': [node], 'CPPPATH': [includes[1], includes[0]], + 'LINKFLAGS': '--keep=x', 'CFLAGS': '-std=c99 -gnu'} + keil.MDK45Project({'CPPPATH': includes, 'CPPDEFINES': ['TEST']}, + tree, os.path.join(temp, 'project.uvprojx'), [group]) + targets = tree.findall('Targets/Target') + for target in targets: + options = target.find('TargetOption/TargetArmAds') + self.assertEqual(options.findtext('Cads/VariousControls/IncludePath'), 'first;second') + self.assertEqual(options.findtext('Cads/VariousControls/Define'), 'TEST') + self.assertEqual(options.findtext('Cads/uC99'), '1') + self.assertEqual(options.findtext('Cads/uGnu'), '1') + self.assertEqual(options.findtext('LDads/Misc'), '--keep=x') + self.assertEqual(target.findtext('Groups/Group/GroupName'), 'test') + self.assertEqual(target.findtext('Groups/Group/Files/File/FileName'), 'main.c') + + def test_iar_linker_symbols_and_other_options(self): + iar = load_target('iar') + template = ROOT / 'bsp' / 'nxp' / 'imx' / 'imxrt' / 'imxrt1180-nxp-evk' / 'cm33' / 'template.ewp' + with tempfile.TemporaryDirectory() as temp: + shutil.copyfile(template, os.path.join(temp, 'template.ewp')) + template_tree = etree.parse(os.path.join(temp, 'template.ewp')) + for option in template_tree.findall('configuration/settings/data/option'): + if option.findtext('name') == 'IlinkExtraOptions': + option.find('state').text = '--existing' + template_tree.write(os.path.join(temp, 'template.ewp'), encoding='utf-8') + previous = os.getcwd() + try: + os.chdir(temp) + groups = [ + {'name': 'one', 'src': [], 'LINKFLAGS': '--keep=Reset_Handler --config_def APP_OFFSET=0x1000'}, + {'name': 'two', 'src': [], 'LINKFLAGS': '--config_def=MODE=2 --entry "my entry"'}, + ] + with mock.patch.dict(sys.modules, {'rtconfig': types.ModuleType('rtconfig')}): + iar.IARProject({}, 'project.ewp', groups) + finally: + os.chdir(previous) + tree = etree.parse(os.path.join(temp, 'project.ewp')) + for config in tree.findall('configuration'): + options = {o.findtext('name'): o for o in config.findall('settings/data/option')} + defines = [s.text for s in options['IlinkConfigDefines'].findall('state') if s.text] + self.assertEqual(defines, ['APP_OFFSET=0x1000', 'MODE=2']) + self.assertEqual(options['IlinkUseExtraOptions'].findtext('state'), '1') + self.assertEqual(options['IlinkExtraOptions'].findtext('state'), + '--existing --keep=Reset_Handler --entry "my entry"') + + def test_iar_invalid_or_quoted_linker_flags(self): + iar = load_target('iar') + defines, extra = iar._split_iar_linkflags( + '--keep=Reset_Handler --config_def "NAME=2" --log "C:\\Program Files\\log.txt"') + self.assertEqual(defines, ['NAME=2']) + self.assertEqual(extra, '--keep=Reset_Handler --log "C:\\Program Files\\log.txt"') + for flags in ['--config_def', '--config_def --keep=Reset_Handler', '--log "broken']: + with self.subTest(flags=flags), self.assertRaises(ValueError): + iar._split_iar_linkflags(flags) + + def test_iar_wsdt_repeated_utf8_updates(self): + iar = load_target('iar') + with tempfile.TemporaryDirectory() as temp: + wsdt = os.path.join(temp, 'settings', 'project.wsdt') + for config in ['ram', 'nor', 'hyperram']: + iar._update_iar_wsdt(wsdt, 'project & test', config) + self.assertIn(b"encoding='utf-8'", Path(wsdt).read_bytes()) + tree = etree.parse(wsdt) + self.assertEqual(tree.findtext('ConfigDictionary/CurrentConfigs/Project'), 'project & test/' + config) + + +if __name__ == '__main__': + unittest.main() From c5e01ecadda7c4beca66a2c15d63bcea1363b8c4 Mon Sep 17 00:00:00 2001 From: nxp-ran Date: Fri, 13 Nov 2026 15:07:25 +0800 Subject: [PATCH 5/5] [tools] Remove IDE target regression test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/testcases/test_ide_targets.py | 104 ---------------------------- 1 file changed, 104 deletions(-) delete mode 100644 tools/testcases/test_ide_targets.py diff --git a/tools/testcases/test_ide_targets.py b/tools/testcases/test_ide_targets.py deleted file mode 100644 index deedcafa679d..000000000000 --- a/tools/testcases/test_ide_targets.py +++ /dev/null @@ -1,104 +0,0 @@ -import importlib.util -import os -import shutil -import sys -import tempfile -import types -import unittest -import xml.etree.ElementTree as etree -from pathlib import Path -from unittest import mock - - -ROOT = Path(__file__).resolve().parents[2] -sys.path.insert(0, str(ROOT / 'tools')) - - -def load_target(name): - spec = importlib.util.spec_from_file_location(name, ROOT / 'tools' / 'targets' / (name + '.py')) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -class IDETargetTests(unittest.TestCase): - def test_keil_targets_preserve_include_order(self): - keil = load_target('keil') - target_xml = ''' - 00 - - - ''' - tree = etree.ElementTree(etree.fromstring('' + target_xml * 2 + '')) - with tempfile.TemporaryDirectory() as temp: - includes = [os.path.join(temp, 'first'), os.path.join(temp, 'second')] - source = types.SimpleNamespace(name='main.c', abspath=os.path.join(temp, 'main.c')) - node = types.SimpleNamespace(rfile=lambda: source) - group = {'name': 'test', 'src': [node], 'CPPPATH': [includes[1], includes[0]], - 'LINKFLAGS': '--keep=x', 'CFLAGS': '-std=c99 -gnu'} - keil.MDK45Project({'CPPPATH': includes, 'CPPDEFINES': ['TEST']}, - tree, os.path.join(temp, 'project.uvprojx'), [group]) - targets = tree.findall('Targets/Target') - for target in targets: - options = target.find('TargetOption/TargetArmAds') - self.assertEqual(options.findtext('Cads/VariousControls/IncludePath'), 'first;second') - self.assertEqual(options.findtext('Cads/VariousControls/Define'), 'TEST') - self.assertEqual(options.findtext('Cads/uC99'), '1') - self.assertEqual(options.findtext('Cads/uGnu'), '1') - self.assertEqual(options.findtext('LDads/Misc'), '--keep=x') - self.assertEqual(target.findtext('Groups/Group/GroupName'), 'test') - self.assertEqual(target.findtext('Groups/Group/Files/File/FileName'), 'main.c') - - def test_iar_linker_symbols_and_other_options(self): - iar = load_target('iar') - template = ROOT / 'bsp' / 'nxp' / 'imx' / 'imxrt' / 'imxrt1180-nxp-evk' / 'cm33' / 'template.ewp' - with tempfile.TemporaryDirectory() as temp: - shutil.copyfile(template, os.path.join(temp, 'template.ewp')) - template_tree = etree.parse(os.path.join(temp, 'template.ewp')) - for option in template_tree.findall('configuration/settings/data/option'): - if option.findtext('name') == 'IlinkExtraOptions': - option.find('state').text = '--existing' - template_tree.write(os.path.join(temp, 'template.ewp'), encoding='utf-8') - previous = os.getcwd() - try: - os.chdir(temp) - groups = [ - {'name': 'one', 'src': [], 'LINKFLAGS': '--keep=Reset_Handler --config_def APP_OFFSET=0x1000'}, - {'name': 'two', 'src': [], 'LINKFLAGS': '--config_def=MODE=2 --entry "my entry"'}, - ] - with mock.patch.dict(sys.modules, {'rtconfig': types.ModuleType('rtconfig')}): - iar.IARProject({}, 'project.ewp', groups) - finally: - os.chdir(previous) - tree = etree.parse(os.path.join(temp, 'project.ewp')) - for config in tree.findall('configuration'): - options = {o.findtext('name'): o for o in config.findall('settings/data/option')} - defines = [s.text for s in options['IlinkConfigDefines'].findall('state') if s.text] - self.assertEqual(defines, ['APP_OFFSET=0x1000', 'MODE=2']) - self.assertEqual(options['IlinkUseExtraOptions'].findtext('state'), '1') - self.assertEqual(options['IlinkExtraOptions'].findtext('state'), - '--existing --keep=Reset_Handler --entry "my entry"') - - def test_iar_invalid_or_quoted_linker_flags(self): - iar = load_target('iar') - defines, extra = iar._split_iar_linkflags( - '--keep=Reset_Handler --config_def "NAME=2" --log "C:\\Program Files\\log.txt"') - self.assertEqual(defines, ['NAME=2']) - self.assertEqual(extra, '--keep=Reset_Handler --log "C:\\Program Files\\log.txt"') - for flags in ['--config_def', '--config_def --keep=Reset_Handler', '--log "broken']: - with self.subTest(flags=flags), self.assertRaises(ValueError): - iar._split_iar_linkflags(flags) - - def test_iar_wsdt_repeated_utf8_updates(self): - iar = load_target('iar') - with tempfile.TemporaryDirectory() as temp: - wsdt = os.path.join(temp, 'settings', 'project.wsdt') - for config in ['ram', 'nor', 'hyperram']: - iar._update_iar_wsdt(wsdt, 'project & test', config) - self.assertIn(b"encoding='utf-8'", Path(wsdt).read_bytes()) - tree = etree.parse(wsdt) - self.assertEqual(tree.findtext('ConfigDictionary/CurrentConfigs/Project'), 'project & test/' + config) - - -if __name__ == '__main__': - unittest.main()