77# Date Author Notes
88# 2019-03-21 Bernard the first version
99# 2019-04-15 armink fix project update error
10+ # 2025-09-01 wdfk-prog Add project-name support for Eclipse target and enhance folder linking
1011#
1112
1213import glob
@@ -355,32 +356,73 @@ def HandleToolOption(tools, env, project, reset):
355356
356357 return
357358
358-
359359def UpdateProjectStructure (env , prj_name ):
360- bsp_root = env ['BSP_ROOT' ]
361- rtt_root = env ['RTT_ROOT' ]
362-
363- project = etree .parse ('.project' )
364- root = project .getroot ()
365-
366- if rtt_root .startswith (bsp_root ):
367- linkedResources = root .find ('linkedResources' )
368- if linkedResources == None :
369- linkedResources = SubElement (root , 'linkedResources' )
370-
371- links = linkedResources .findall ('link' )
372- # delete all RT-Thread folder links
373- for link in links :
374- if link .find ('name' ).text .startswith ('rt-thread' ):
375- linkedResources .remove (link )
376-
377- if prj_name :
378- name = root .find ('name' )
379- if name == None :
380- name = SubElement (root , 'name' )
381- name .text = prj_name
360+ """
361+ Updates the .project file to link any external/specified folders from
362+ PROJECT_SOURCE_FOLDERS in rtconfig.py. This version correctly handles
363+ all specified paths, regardless of their physical location.
364+ """
365+ bsp_root = os .path .abspath (env ['BSP_ROOT' ])
366+
367+ # --- 1. Read the list of folders to be linked from rtconfig.py ---
368+ folders_to_link = []
369+ try :
370+ import rtconfig
371+ if hasattr (rtconfig , 'PROJECT_SOURCE_FOLDERS' ) and rtconfig .PROJECT_SOURCE_FOLDERS :
372+ folders_to_link = rtconfig .PROJECT_SOURCE_FOLDERS
373+ except ImportError :
374+ pass # It's okay if the file or variable doesn't exist.
382375
383- out = open ('.project' , 'w' )
376+ if not os .path .exists ('.project' ):
377+ print ("Error: .project file not found. Cannot update." )
378+ return
379+
380+ project_xml = etree .parse ('.project' )
381+ root = project_xml .getroot ()
382+
383+ # --- 2. Ensure the <linkedResources> node exists ---
384+ linkedResources = root .find ('linkedResources' )
385+ if linkedResources is None :
386+ linkedResources = SubElement (root , 'linkedResources' )
387+
388+ # --- 3. Clean up previously managed links to prevent duplicates ---
389+ managed_link_names = [os .path .basename (p ) for p in folders_to_link ]
390+ for link in list (linkedResources .findall ('link' )): # Use list() to safely remove items while iterating
391+ name_element = link .find ('name' )
392+ if name_element is not None and name_element .text in managed_link_names :
393+ linkedResources .remove (link )
394+ print (f"Removed existing linked resource '{ name_element .text } ' to regenerate it." )
395+
396+ # --- 4. Create new links for each folder specified by the user ---
397+ for folder_path in folders_to_link :
398+ # The link name in the IDE will be the directory's base name.
399+ link_name = os .path .basename (folder_path )
400+
401+ # Resolve the absolute path of the folder to be linked.
402+ abs_folder_path = os .path .abspath (os .path .join (bsp_root , folder_path ))
403+
404+ print (f"Creating linked resource for '{ link_name } ' pointing to '{ abs_folder_path } '..." )
405+
406+ # Calculate the URI relative to the Eclipse ${PROJECT_LOC} variable.
407+ # This works for both internal and external folders.
408+ relative_link_path = os .path .relpath (abs_folder_path , bsp_root ).replace ('\\ ' , '/' )
409+
410+ # Use Eclipse path variables for robustness. PARENT_LOC is more standard for external folders.
411+ if relative_link_path .startswith ('../' ):
412+ # Count how many levels up
413+ levels_up = relative_link_path .count ('../' )
414+ clean_path = relative_link_path .replace ('../' , '' )
415+ location_uri = f'PARENT-{ levels_up } -PROJECT_LOC/{ clean_path } '
416+ else :
417+ location_uri = f'PROJECT_LOC/{ relative_link_path } '
418+
419+ link_element = SubElement (linkedResources , 'link' )
420+ SubElement (link_element , 'name' ).text = link_name
421+ SubElement (link_element , 'type' ).text = '2' # Type 2 means a folder link.
422+ SubElement (link_element , 'locationURI' ).text = location_uri
423+
424+ # --- 5. Write the updated content back to the .project file ---
425+ out = open ('.project' , 'w' , encoding = 'utf-8' )
384426 out .write ('<?xml version="1.0" encoding="UTF-8"?>\n ' )
385427 xml_indent (root )
386428 out .write (etree .tostring (root , encoding = 'utf-8' ).decode ('utf-8' ))
@@ -492,6 +534,57 @@ def HandleExcludingOption(entry, sourceEntries, excluding):
492534
493535 SubElement (sourceEntries , 'entry' , {'excluding' : value , 'flags' : 'VALUE_WORKSPACE_PATH|RESOLVED' , 'kind' :'sourcePath' , 'name' :"" })
494536
537+ def UpdateProjectName (prj_name ):
538+ """
539+ Regardless of whether the .project file exists, make sure its name is correct.
540+ """
541+ if not prj_name :
542+ return
543+
544+ try :
545+ if not os .path .exists ('.project' ):
546+ if rt_studio .gen_project_file (os .path .abspath (".project" ), prj_name ) is False :
547+ print ('Fail!' )
548+ return
549+ print ("Generated .project file with name:" , prj_name )
550+
551+ project_tree = etree .parse ('.project' )
552+ root = project_tree .getroot ()
553+ name_element = root .find ('name' )
554+
555+ if name_element is not None and name_element .text != prj_name :
556+ print (f"Updating project name from '{ name_element .text } ' to '{ prj_name } '..." )
557+ name_element .text = prj_name
558+
559+ project_tree .write ('.project' , encoding = 'UTF-8' , xml_declaration = True )
560+ xml_indent (root )
561+ with open ('.project' , 'w' , encoding = 'utf-8' ) as f :
562+ f .write ('<?xml version="1.0" encoding="UTF-8"?>\n ' )
563+ f .write (etree .tostring (root , encoding = 'utf-8' ).decode ('utf-8' ))
564+
565+ except Exception as e :
566+ print ("Error updating .project file:" , e )
567+
568+ def HandleSourceEntries_Global (sourceEntries , excluding ):
569+ """
570+ Configure the project to include the root folder ("") and exclude all
571+ files/folders in the 'excluding' list. This makes all project folders
572+ visible in the IDE.
573+ """
574+ # To keep the configuration clean, first remove all existing entries
575+ for entry in sourceEntries .findall ('entry' ):
576+ sourceEntries .remove (entry )
577+
578+ # Join the exclusion list into a single string with the '|' separator
579+ excluding_str = '|' .join (sorted (excluding ))
580+
581+ # Create a new, single entry for the project root directory
582+ SubElement (sourceEntries , 'entry' , {
583+ 'flags' : 'VALUE_WORKSPACE_PATH|RESOLVED' ,
584+ 'kind' : 'sourcePath' ,
585+ 'name' : "" , # An empty string "" represents the project root
586+ 'excluding' : excluding_str
587+ })
495588
496589def UpdateCproject (env , project , excluding , reset , prj_name ):
497590 excluding = sorted (excluding )
@@ -504,31 +597,39 @@ def UpdateCproject(env, project, excluding, reset, prj_name):
504597 tools = cconfiguration .findall ('storageModule/configuration/folderInfo/toolChain/tool' )
505598 HandleToolOption (tools , env , project , reset )
506599
600+ if prj_name :
601+ config_element = cconfiguration .find ('storageModule/configuration' )
602+ if config_element is not None :
603+ config_element .set ('artifactName' , prj_name )
604+
507605 sourceEntries = cconfiguration .find ('storageModule/configuration/sourceEntries' )
508- if sourceEntries != None :
509- entry = sourceEntries .find ('entry' )
510- HandleExcludingOption (entry , sourceEntries , excluding )
511- # update refreshScope
606+ if sourceEntries is not None :
607+ # Call the new global handler function for source entries
608+ HandleSourceEntries_Global (sourceEntries , excluding )
609+
610+ # update refreshScope to ensure the project refreshes correctly
512611 if prj_name :
513- prj_name = '/' + prj_name
612+ prj_name_for_path = '/' + prj_name
514613 configurations = root .findall ('storageModule/configuration' )
515614 for configuration in configurations :
516615 resource = configuration .find ('resource' )
517- configuration .remove (resource )
518- SubElement (configuration , 'resource' , {'resourceType' : "PROJECT" , 'workspacePath' : prj_name })
616+ if resource is not None :
617+ configuration .remove (resource )
618+ SubElement (configuration , 'resource' , {'resourceType' : "PROJECT" , 'workspacePath' : prj_name_for_path })
519619
520- # write back to .cproject
620+ # write back to .cproject file
521621 out = open ('.cproject' , 'w' )
522622 out .write ('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n ' )
523623 out .write ('<?fileVersion 4.0.0?>' )
524624 xml_indent (root )
525625 out .write (etree .tostring (root , encoding = 'utf-8' ).decode ('utf-8' ))
526626 out .close ()
527627
528-
529- def TargetEclipse (env , reset = False , prj_name = None ):
628+ def TargetEclipse (env , project , reset = False , prj_name = None ):
530629 global source_pattern
531630
631+ UpdateProjectName (prj_name )
632+
532633 print ('Update eclipse setting...' )
533634
534635 # generate cproject file
@@ -561,7 +662,7 @@ def TargetEclipse(env, reset=False, prj_name=None):
561662 # enable lowwer .s file compiled in eclipse cdt
562663 if not os .path .exists ('.settings/org.eclipse.core.runtime.prefs' ):
563664 if rt_studio .gen_org_eclipse_core_runtime_prefs (
564- os .path .abspath (".settings/org.eclipse.core.runtime.prefs" )) is False :
665+ os .path .abspath (".settings/org.eclipse.core.runtime.prefs" )) is False :
565666 print ('Fail!' )
566667 return
567668
@@ -571,8 +672,6 @@ def TargetEclipse(env, reset=False, prj_name=None):
571672 print ('Fail!' )
572673 return
573674
574- project = ProjectInfo (env )
575-
576675 # update the project file structure info on '.project' file
577676 UpdateProjectStructure (env , prj_name )
578677
0 commit comments