diff --git a/tools/building.py b/tools/building.py index d141797de42..2abf8de6050 100644 --- a/tools/building.py +++ b/tools/building.py @@ -27,6 +27,7 @@ # 2025-01-05 Bernard Add logging as Env['log'] # 2025-03-02 ZhaoCake Add MkDist_Strip # 2025-01-05 Assistant Refactor SCons PreProcessor patch to independent class +# 2025-09-01 wdfk-prog Add project-name option for custom project naming in IDE targets import os import sys @@ -802,19 +803,21 @@ def local_group(group, objects): EndBuilding(target, program) -def GenTargetProject(program = None): +def GenTargetProject(program = None, project_name = None): + if project_name is None: + project_name = GetOption('project-name') if GetOption('target') in ['mdk', 'mdk4', 'mdk5']: from targets.keil import MDK2Project, MDK4Project, MDK5Project, ARMCC_Version if os.path.isfile('template.uvprojx') and GetOption('target') not in ['mdk4']: # Keil5 - MDK5Project(Env, GetOption('project-name') + '.uvprojx', Projects) + MDK5Project(Env, project_name + '.uvprojx', Projects) print("Keil5 project is generating...") elif os.path.isfile('template.uvproj') and GetOption('target') not in ['mdk5']: # Keil4 - MDK4Project(Env, GetOption('project-name') + '.uvproj', Projects) + MDK4Project(Env, project_name + '.uvproj', Projects) print("Keil4 project is generating...") elif os.path.isfile('template.Uv2') and GetOption('target') not in ['mdk4', 'mdk5']: # Keil2 - MDK2Project(Env, GetOption('project-name') + '.Uv2', Projects) + MDK2Project(Env, project_name + '.Uv2', Projects) print("Keil2 project is generating...") else: print ('No template project file found.') @@ -825,20 +828,20 @@ def GenTargetProject(program = None): if GetOption('target') == 'iar': from targets.iar import IARProject, IARVersion print("IAR Version: " + IARVersion()) - IARProject(Env, GetOption('project-name') + '.ewp', Projects) + IARProject(Env, project_name + '.ewp', Projects) print("IAR project has generated successfully!") if GetOption('target') == 'vs': from targets.vs import VSProject - VSProject(GetOption('project-name') + '.vcproj', Projects, program) + VSProject(project_name + '.vcproj', Projects, program) if GetOption('target') == 'vs2012': from targets.vs2012 import VS2012Project - VS2012Project(GetOption('project-name') + '.vcxproj', Projects, program) + VS2012Project(project_name + '.vcxproj', Projects, program) if GetOption('target') == 'cb': from targets.codeblocks import CBProject - CBProject(GetOption('project-name') + '.cbp', Projects, program) + CBProject(project_name + '.cbp', Projects, program) if GetOption('target') == 'ua': from targets.ua import PrepareUA @@ -857,7 +860,7 @@ def GenTargetProject(program = None): if GetOption('target') == 'cdk': from targets.cdk import CDKProject - CDKProject(GetOption('project-name') + '.cdkproj', Projects) + CDKProject(project_name + '.cdkproj', Projects) if GetOption('target') == 'ses': from targets.ses import SESProject @@ -869,7 +872,9 @@ def GenTargetProject(program = None): if GetOption('target') == 'eclipse': from targets.eclipse import TargetEclipse - TargetEclipse(Env, GetOption('reset-project-config'), GetOption('project-name')) + from utils import ProjectInfo + project = ProjectInfo(Env) + TargetEclipse(Env, project, GetOption('reset-project-config'), project_name) if GetOption('target') == 'codelite': from targets.codelite import TargetCodelite @@ -877,7 +882,7 @@ def GenTargetProject(program = None): if GetOption('target') == 'cmake' or GetOption('target') == 'cmake-armclang': from targets.cmake import CMakeProject - CMakeProject(Env, Projects, GetOption('project-name')) + CMakeProject(Env, Projects, project_name) if GetOption('target') == 'xmake': from targets.xmake import XMakeProject @@ -912,13 +917,20 @@ def EndBuilding(target, program = None): Clean(target, 'rtua.pyc') Clean(target, '.sconsign.dblite') + # Priority: 1. PROJECT_NAME in rtconfig.py -> + # 2. --project-name command line option -> + # 3. SCons default value ('project') + project_name = GetOption('project-name') + if hasattr(rtconfig, 'PROJECT_NAME') and rtconfig.PROJECT_NAME: + project_name = rtconfig.PROJECT_NAME + + if GetOption('target'): - GenTargetProject(program) + GenTargetProject(program, project_name) need_exit = True BSP_ROOT = Dir('#').abspath - project_name = GetOption('project-name') project_path = GetOption('project-path') # 合并处理打包相关选项 diff --git a/tools/targets/eclipse.py b/tools/targets/eclipse.py index 050d8a184f3..84831340e94 100644 --- a/tools/targets/eclipse.py +++ b/tools/targets/eclipse.py @@ -7,6 +7,7 @@ # Date Author Notes # 2019-03-21 Bernard the first version # 2019-04-15 armink fix project update error +# 2025-09-01 wdfk-prog Add project-name support for Eclipse target and enhance folder linking # import glob @@ -355,32 +356,73 @@ def HandleToolOption(tools, env, project, reset): return - def UpdateProjectStructure(env, prj_name): - bsp_root = env['BSP_ROOT'] - rtt_root = env['RTT_ROOT'] - - project = etree.parse('.project') - root = project.getroot() - - if rtt_root.startswith(bsp_root): - linkedResources = root.find('linkedResources') - if linkedResources == None: - linkedResources = SubElement(root, 'linkedResources') - - links = linkedResources.findall('link') - # delete all RT-Thread folder links - for link in links: - if link.find('name').text.startswith('rt-thread'): - linkedResources.remove(link) - - if prj_name: - name = root.find('name') - if name == None: - name = SubElement(root, 'name') - name.text = prj_name + """ + Updates the .project file to link any external/specified folders from + PROJECT_SOURCE_FOLDERS in rtconfig.py. This version correctly handles + all specified paths, regardless of their physical location. + """ + bsp_root = os.path.abspath(env['BSP_ROOT']) + + # --- 1. Read the list of folders to be linked from rtconfig.py --- + folders_to_link = [] + try: + import rtconfig + if hasattr(rtconfig, 'PROJECT_SOURCE_FOLDERS') and rtconfig.PROJECT_SOURCE_FOLDERS: + folders_to_link = rtconfig.PROJECT_SOURCE_FOLDERS + except ImportError: + pass # It's okay if the file or variable doesn't exist. - out = open('.project', 'w') + if not os.path.exists('.project'): + print("Error: .project file not found. Cannot update.") + return + + project_xml = etree.parse('.project') + root = project_xml.getroot() + + # --- 2. Ensure the node exists --- + linkedResources = root.find('linkedResources') + if linkedResources is None: + linkedResources = SubElement(root, 'linkedResources') + + # --- 3. Clean up previously managed links to prevent duplicates --- + managed_link_names = [os.path.basename(p) for p in folders_to_link] + for link in list(linkedResources.findall('link')): # Use list() to safely remove items while iterating + name_element = link.find('name') + if name_element is not None and name_element.text in managed_link_names: + linkedResources.remove(link) + print(f"Removed existing linked resource '{name_element.text}' to regenerate it.") + + # --- 4. Create new links for each folder specified by the user --- + for folder_path in folders_to_link: + # The link name in the IDE will be the directory's base name. + link_name = os.path.basename(folder_path) + + # Resolve the absolute path of the folder to be linked. + abs_folder_path = os.path.abspath(os.path.join(bsp_root, folder_path)) + + print(f"Creating linked resource for '{link_name}' pointing to '{abs_folder_path}'...") + + # Calculate the URI relative to the Eclipse ${PROJECT_LOC} variable. + # This works for both internal and external folders. + relative_link_path = os.path.relpath(abs_folder_path, bsp_root).replace('\\', '/') + + # Use Eclipse path variables for robustness. PARENT_LOC is more standard for external folders. + if relative_link_path.startswith('../'): + # Count how many levels up + levels_up = relative_link_path.count('../') + clean_path = relative_link_path.replace('../', '') + location_uri = f'PARENT-{levels_up}-PROJECT_LOC/{clean_path}' + else: + location_uri = f'PROJECT_LOC/{relative_link_path}' + + link_element = SubElement(linkedResources, 'link') + SubElement(link_element, 'name').text = link_name + SubElement(link_element, 'type').text = '2' # Type 2 means a folder link. + SubElement(link_element, 'locationURI').text = location_uri + + # --- 5. Write the updated content back to the .project file --- + out = open('.project', 'w', encoding='utf-8') out.write('\n') xml_indent(root) out.write(etree.tostring(root, encoding='utf-8').decode('utf-8')) @@ -392,76 +434,111 @@ def UpdateProjectStructure(env, prj_name): def GenExcluding(env, project): rtt_root = os.path.abspath(env['RTT_ROOT']) bsp_root = os.path.abspath(env['BSP_ROOT']) + + abs_source_folders = [] + try: + import rtconfig + if hasattr(rtconfig, 'PROJECT_SOURCE_FOLDERS'): + for folder in rtconfig.PROJECT_SOURCE_FOLDERS: + abs_source_folders.append(os.path.abspath(os.path.join(bsp_root, folder))) + except ImportError: + pass + coll_dirs = CollectPaths(project['DIRS']) all_paths_temp = [OSPath(path) for path in coll_dirs] all_paths = [] # add used path for path in all_paths_temp: - if path.startswith(rtt_root) or path.startswith(bsp_root): + is_valid_path = False + # Check whether the path is within BSP, RTT, or any external source folder. + if path.lower().startswith(bsp_root.lower()) or path.lower().startswith(rtt_root.lower()): + is_valid_path = True + else: + for source_folder in abs_source_folders: + if path.lower().startswith(source_folder.lower()): + is_valid_path = True + break + + if is_valid_path: all_paths.append(path) - if bsp_root.startswith(rtt_root): - # bsp folder is in the RT-Thread root folder, such as the RT-Thread source code on GitHub - exclude_paths = ExcludePaths(rtt_root, all_paths) - elif rtt_root.startswith(bsp_root): - # RT-Thread root folder is in the bsp folder, such as project folder which generate by 'scons --dist' cmd - check_path = [] - exclude_paths = [] - # analyze the primary folder which relative to BSP_ROOT and in all_paths - for path in all_paths: - if path.startswith(bsp_root): - folders = RelativeProjectPath(env, path).split('\\') - if folders[0] != '.' and '\\' + folders[0] not in check_path: - check_path += ['\\' + folders[0]] - # exclue the folder which has managed by scons - for path in check_path: - exclude_paths += ExcludePaths(bsp_root + path, all_paths) - else: - exclude_paths = ExcludePaths(rtt_root, all_paths) - exclude_paths += ExcludePaths(bsp_root, all_paths) - paths = exclude_paths + # Exclude the entire unused directory to support external folders. exclude_paths = [] - # remove the folder which not has source code by source_pattern - for path in paths: - # add bsp and libcpu folder and not collect source files (too more files) + + # 1. Exclude unused directories under BSP ROOT + exclude_paths += ExcludePaths(bsp_root, all_paths) + + # 2. Exclude unused directories under RTT ROOT (if it is not within the BSP) + if not rtt_root.lower().startswith(bsp_root.lower()): + exclude_paths += ExcludePaths(rtt_root, all_paths) + + # 3. Exclude all unused directories under external folders. + for folder in abs_source_folders: + # Avoid reprocessing folders that have already been processed as RTT_ROOT. + if folder.lower() != rtt_root.lower(): + exclude_paths += ExcludePaths(folder, all_paths) + + # Filter out the "unused" directories that do not actually have source files. + filtered_exclude_paths = [] + for path in exclude_paths: if path.endswith('rt-thread\\bsp') or path.endswith('rt-thread\\libcpu'): - exclude_paths += [path] + filtered_exclude_paths.append(path) continue + if len(CollectAllFilesinPath(path, source_pattern)): + filtered_exclude_paths.append(path) - set = CollectAllFilesinPath(path, source_pattern) - if len(set): - exclude_paths += [path] - - exclude_paths = [RelativeProjectPath(env, path).replace('\\', '/') for path in exclude_paths] + # Convert the path to a project relative path. + exclude_paths_relative = [RelativeProjectPath(env, path).replace('\\', '/') for path in filtered_exclude_paths] + # Calculate the individual files that need to be excluded. all_files = CollectFiles(all_paths, source_pattern) src_files = project['FILES'] exclude_files = ExcludeFiles(all_files, src_files) - exclude_files = [RelativeProjectPath(env, file).replace('\\', '/') for file in exclude_files] + exclude_files_relative = [RelativeProjectPath(env, file).replace('\\', '/') for file in exclude_files] - env['ExPaths'] = exclude_paths - env['ExFiles'] = exclude_files + env['ExPaths'] = exclude_paths_relative + env['ExFiles'] = exclude_files_relative - return exclude_paths + exclude_files + return exclude_paths_relative + exclude_files_relative +def RelativeProjectPath(env, path): + clean_path_str = str(path).strip().strip(',').strip('"') + + try: + abs_path = os.path.abspath(clean_path_str) + except Exception: + return clean_path_str -def RelativeProjectPath(env, path): project_root = os.path.abspath(env['BSP_ROOT']) + + # 1. Check if the path is within the project root directory (BSP_ROOT) + if abs_path.lower().startswith(project_root.lower()): + return _make_path_relative(project_root, abs_path) + + # 2. Check if the path is within the RT-Thread root directory. rtt_root = os.path.abspath(env['RTT_ROOT']) + if abs_path.lower().startswith(rtt_root.lower()): + return 'rt-thread/' + _make_path_relative(rtt_root, abs_path) + + # 3. Check the PROJECT_SOURCE_FOLDERS defined in rtconfig.py. + if hasattr(rtconfig, 'PROJECT_SOURCE_FOLDERS'): + for folder_entry in rtconfig.PROJECT_SOURCE_FOLDERS: + # Get the absolute path of the source folder (for example 'E:/.../lib') + abs_source_folder = os.path.abspath(os.path.join(project_root, folder_entry)) - if path.startswith(project_root): - return _make_path_relative(project_root, path) + if abs_path.lower().startswith(abs_source_folder.lower()): + # The link name in the project is the base name of the folder path (for example, '../lib' -> 'lib') + link_name = os.path.basename(os.path.normpath(folder_entry)) - if path.startswith(rtt_root): - return 'rt-thread/' + _make_path_relative(rtt_root, path) + relative_part = _make_path_relative(abs_source_folder, abs_path) - # TODO add others folder - print('ERROR: the ' + path + ' not support') + return os.path.join(link_name, relative_part).replace('\\', '/') - return path + print(f'WARNING: The path "{path}" could not be made relative to the project.') + return clean_path_str def HandleExcludingOption(entry, sourceEntries, excluding): @@ -492,6 +569,57 @@ def HandleExcludingOption(entry, sourceEntries, excluding): SubElement(sourceEntries, 'entry', {'excluding': value, 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', 'kind':'sourcePath', 'name':""}) +def UpdateProjectName(prj_name): + """ + Regardless of whether the .project file exists, make sure its name is correct. + """ + if not prj_name: + return + + try: + if not os.path.exists('.project'): + if rt_studio.gen_project_file(os.path.abspath(".project"), prj_name) is False: + print('Fail!') + return + print("Generated .project file with name:", prj_name) + + project_tree = etree.parse('.project') + root = project_tree.getroot() + name_element = root.find('name') + + if name_element is not None and name_element.text != prj_name: + print(f"Updating project name from '{name_element.text}' to '{prj_name}'...") + name_element.text = prj_name + + project_tree.write('.project', encoding='UTF-8', xml_declaration=True) + xml_indent(root) + with open('.project', 'w', encoding='utf-8') as f: + f.write('\n') + f.write(etree.tostring(root, encoding='utf-8').decode('utf-8')) + + except Exception as e: + print("Error updating .project file:", e) + +def HandleSourceEntries_Global(sourceEntries, excluding): + """ + Configure the project to include the root folder ("") and exclude all + files/folders in the 'excluding' list. This makes all project folders + visible in the IDE. + """ + # To keep the configuration clean, first remove all existing entries + for entry in sourceEntries.findall('entry'): + sourceEntries.remove(entry) + + # Join the exclusion list into a single string with the '|' separator + excluding_str = '|'.join(sorted(excluding)) + + # Create a new, single entry for the project root directory + SubElement(sourceEntries, 'entry', { + 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', + 'kind': 'sourcePath', + 'name': "", # An empty string "" represents the project root + 'excluding': excluding_str + }) def UpdateCproject(env, project, excluding, reset, prj_name): excluding = sorted(excluding) @@ -504,20 +632,38 @@ def UpdateCproject(env, project, excluding, reset, prj_name): tools = cconfiguration.findall('storageModule/configuration/folderInfo/toolChain/tool') HandleToolOption(tools, env, project, reset) + if prj_name: + config_element = cconfiguration.find('storageModule/configuration') + if config_element is not None: + config_element.set('artifactName', prj_name) + + pre_build_step = getattr(rtconfig, 'PRE_BUILD_STEP', None) + post_build_step = getattr(rtconfig, 'POST_BUILD_STEP', None) + + if pre_build_step: + config_element.set('prebuildStep', pre_build_step) + print("Setting/Overwriting Pre-build step...") + + if post_build_step: + config_element.set('postbuildStep', post_build_step) + print("Setting/Overwriting Post-build step...") + sourceEntries = cconfiguration.find('storageModule/configuration/sourceEntries') - if sourceEntries != None: - entry = sourceEntries.find('entry') - HandleExcludingOption(entry, sourceEntries, excluding) - # update refreshScope + if sourceEntries is not None: + # Call the new global handler function for source entries + HandleSourceEntries_Global(sourceEntries, excluding) + + # update refreshScope to ensure the project refreshes correctly if prj_name: - prj_name = '/' + prj_name + prj_name_for_path = '/' + prj_name configurations = root.findall('storageModule/configuration') for configuration in configurations: resource = configuration.find('resource') - configuration.remove(resource) - SubElement(configuration, 'resource', {'resourceType': "PROJECT", 'workspacePath': prj_name}) + if resource is not None: + configuration.remove(resource) + SubElement(configuration, 'resource', {'resourceType': "PROJECT", 'workspacePath': prj_name_for_path}) - # write back to .cproject + # write back to .cproject file out = open('.cproject', 'w') out.write('\n') out.write('') @@ -525,10 +671,11 @@ def UpdateCproject(env, project, excluding, reset, prj_name): out.write(etree.tostring(root, encoding='utf-8').decode('utf-8')) out.close() - -def TargetEclipse(env, reset=False, prj_name=None): +def TargetEclipse(env, project, reset=False, prj_name=None): global source_pattern + UpdateProjectName(prj_name) + print('Update eclipse setting...') # generate cproject file @@ -561,7 +708,7 @@ def TargetEclipse(env, reset=False, prj_name=None): # enable lowwer .s file compiled in eclipse cdt if not os.path.exists('.settings/org.eclipse.core.runtime.prefs'): if rt_studio.gen_org_eclipse_core_runtime_prefs( - os.path.abspath(".settings/org.eclipse.core.runtime.prefs")) is False: + os.path.abspath(".settings/org.eclipse.core.runtime.prefs")) is False: print('Fail!') return @@ -571,8 +718,6 @@ def TargetEclipse(env, reset=False, prj_name=None): print('Fail!') return - project = ProjectInfo(env) - # update the project file structure info on '.project' file UpdateProjectStructure(env, prj_name) diff --git a/tools/targets/rt_studio.py b/tools/targets/rt_studio.py index 2f3a4d77408..498e89d721c 100644 --- a/tools/targets/rt_studio.py +++ b/tools/targets/rt_studio.py @@ -328,9 +328,10 @@ def gen_cproject_file(output_file_path): return False -def gen_project_file(output_file_path): +def gen_project_file(output_file_path, project_name): try: - w_str = project_temp + w_str = project_temp.replace('__project_name_flag__', project_name) + dir_name = os.path.dirname(output_file_path) if not os.path.exists(dir_name): os.makedirs(dir_name) @@ -338,6 +339,7 @@ def gen_project_file(output_file_path): f.write(w_str) return True except Exception as e: + print(e) return False