1# 2# Copyright (c) 2006-2022, RT-Thread Development Team 3# 4# SPDX-License-Identifier: Apache-2.0 5# 6# Change Logs: 7# Date Author Notes 8# 2019-03-21 Bernard the first version 9# 2019-04-15 armink fix project update error 10# 11 12import glob 13import xml.etree.ElementTree as etree 14from xml.etree.ElementTree import SubElement 15 16from . import rt_studio 17import sys 18import os 19 20# Add parent directory to path to import building and utils 21sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 22from building import * 23from utils import * 24from utils import _make_path_relative 25from utils import xml_indent 26 27MODULE_VER_NUM = 6 28 29source_pattern = ['*.c', '*.cpp', '*.cxx', '*.cc', '*.s', '*.S', '*.asm','*.cmd'] 30 31 32def OSPath(path): 33 import platform 34 35 if type(path) == type('str'): 36 if platform.system() == 'Windows': 37 return path.replace('/', '\\') 38 else: 39 return path.replace('\\', '/') 40 else: 41 if platform.system() == 'Windows': 42 return [item.replace('/', '\\') for item in path] 43 else: 44 return [item.replace('\\', '/') for item in path] 45 46 47# collect the build source code path and parent path 48def CollectPaths(paths): 49 all_paths = [] 50 51 def ParentPaths(path): 52 ret = os.path.dirname(path) 53 if ret == path or ret == '': 54 return [] 55 56 return [ret] + ParentPaths(ret) 57 58 for path in paths: 59 # path = os.path.abspath(path) 60 path = path.replace('\\', '/') 61 all_paths = all_paths + [path] + ParentPaths(path) 62 63 cwd = os.getcwd() 64 for path in os.listdir(cwd): 65 temp_path = cwd.replace('\\', '/') + '/' + path 66 if os.path.isdir(temp_path): 67 all_paths = all_paths + [temp_path] 68 69 all_paths = list(set(all_paths)) 70 return sorted(all_paths) 71 72 73''' 74Collect all of files under paths 75''' 76 77 78def CollectFiles(paths, pattern): 79 files = [] 80 for path in paths: 81 if type(pattern) == type(''): 82 files = files + glob.glob(path + '/' + pattern) 83 else: 84 for item in pattern: 85 # print('--> %s' % (path + '/' + item)) 86 files = files + glob.glob(path + '/' + item) 87 88 return sorted(files) 89 90 91def CollectAllFilesinPath(path, pattern): 92 files = [] 93 94 for item in pattern: 95 files += glob.glob(path + '/' + item) 96 97 list = os.listdir(path) 98 if len(list): 99 for item in list: 100 if item.startswith('.'): 101 continue 102 if item == 'bsp': 103 continue 104 105 if os.path.isdir(os.path.join(path, item)): 106 files = files + CollectAllFilesinPath(os.path.join(path, item), pattern) 107 return files 108 109 110''' 111Exclude files from infiles 112''' 113 114 115def ExcludeFiles(infiles, files): 116 in_files = set([OSPath(file) for file in infiles]) 117 exl_files = set([OSPath(file) for file in files]) 118 119 exl_files = in_files - exl_files 120 121 return exl_files 122 123 124# caluclate the exclude path for project 125def ExcludePaths(rootpath, paths): 126 ret = [] 127 128 files = os.listdir(OSPath(rootpath)) 129 for file in files: 130 if file.startswith('.'): 131 continue 132 133 fullname = os.path.join(OSPath(rootpath), file) 134 135 if os.path.isdir(fullname): 136 # print(fullname) 137 if not fullname in paths: 138 ret = ret + [fullname] 139 else: 140 ret = ret + ExcludePaths(fullname, paths) 141 142 return ret 143 144 145rtt_path_prefix = '"${workspace_loc://${ProjName}//' 146 147 148def ConverToRttEclipsePathFormat(path): 149 return rtt_path_prefix + path + '}"' 150 151 152def IsRttEclipsePathFormat(path): 153 if path.startswith(rtt_path_prefix): 154 return True 155 else: 156 return False 157 158 159# all libs added by scons should be ends with five whitespace as a flag 160rtt_lib_flag = 5 * " " 161 162 163def ConverToRttEclipseLibFormat(lib): 164 return str(lib) + str(rtt_lib_flag) 165 166 167def IsRttEclipseLibFormat(path): 168 if path.endswith(rtt_lib_flag): 169 return True 170 else: 171 return False 172 173 174def IsCppProject(): 175 return GetDepend('RT_USING_CPLUSPLUS') 176 177 178def HandleToolOption(tools, env, project, reset): 179 is_cpp_prj = IsCppProject() 180 BSP_ROOT = os.path.abspath(env['BSP_ROOT']) 181 182 CPPDEFINES = project['CPPDEFINES'] 183 paths = [ConverToRttEclipsePathFormat(RelativeProjectPath(env, os.path.normpath(i)).replace('\\', '/')) for i in project['CPPPATH']] 184 185 compile_include_paths_options = [] 186 compile_include_files_options = [] 187 compile_defs_options = [] 188 linker_scriptfile_option = None 189 linker_script_option = None 190 linker_nostart_option = None 191 linker_libs_option = None 192 linker_paths_option = None 193 194 linker_newlib_nano_option = None 195 196 for tool in tools: 197 198 if tool.get('id').find('compile') != 1: 199 options = tool.findall('option') 200 # find all compile options 201 for option in options: 202 option_id = option.get('id') 203 if ('compiler.include.paths' in option_id) or ('compiler.option.includepaths' in option_id) or ('compiler.tasking.include' in option_id): 204 compile_include_paths_options += [option] 205 elif option.get('id').find('compiler.include.files') != -1 or option.get('id').find('compiler.option.includefiles') != -1 : 206 compile_include_files_options += [option] 207 elif option.get('id').find('compiler.defs') != -1 or option.get('id').find('compiler.option.definedsymbols') != -1: 208 compile_defs_options += [option] 209 210 if tool.get('id').find('linker') != -1: 211 options = tool.findall('option') 212 # find all linker options 213 for option in options: 214 # the project type and option type must equal 215 if is_cpp_prj != (option.get('id').find('cpp.linker') != -1): 216 continue 217 218 if option.get('id').find('linker.scriptfile') != -1: 219 linker_scriptfile_option = option 220 elif option.get('id').find('linker.option.script') != -1: 221 linker_script_option = option 222 elif option.get('id').find('linker.nostart') != -1: 223 linker_nostart_option = option 224 elif option.get('id').find('linker.libs') != -1: 225 linker_libs_option = option 226 elif option.get('id').find('linker.paths') != -1 and 'LIBPATH' in env: 227 linker_paths_option = option 228 elif option.get('id').find('linker.usenewlibnano') != -1: 229 linker_newlib_nano_option = option 230 231 # change the inclue path 232 for option in compile_include_paths_options: 233 # find all of paths in this project 234 include_paths = option.findall('listOptionValue') 235 for item in include_paths: 236 if reset is True or IsRttEclipsePathFormat(item.get('value')) : 237 # clean old configuration 238 option.remove(item) 239 # print('c.compiler.include.paths') 240 paths = sorted(paths) 241 for item in paths: 242 SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': item}) 243 # change the inclue files (default) or definitions 244 for option in compile_include_files_options: 245 # add '_REENT_SMALL' to CPPDEFINES when --specs=nano.specs has select 246 if linker_newlib_nano_option is not None and linker_newlib_nano_option.get('value') == 'true' and '_REENT_SMALL' not in CPPDEFINES: 247 CPPDEFINES += ['_REENT_SMALL'] 248 249 file_header = ''' 250#ifndef RTCONFIG_PREINC_H__ 251#define RTCONFIG_PREINC_H__ 252 253/* Automatically generated file; DO NOT EDIT. */ 254/* RT-Thread pre-include file */ 255 256''' 257 file_tail = '\n#endif /*RTCONFIG_PREINC_H__*/\n' 258 rtt_pre_inc_item = '"${workspace_loc:/${ProjName}/rtconfig_preinc.h}"' 259 # save the CPPDEFINES in to rtconfig_preinc.h 260 with open('rtconfig_preinc.h', mode = 'w+') as f: 261 f.write(file_header) 262 for cppdef in CPPDEFINES: 263 f.write("#define " + cppdef.replace('=', ' ') + '\n') 264 f.write(file_tail) 265 # change the c.compiler.include.files 266 files = option.findall('listOptionValue') 267 find_ok = False 268 for item in files: 269 if item.get('value') == rtt_pre_inc_item: 270 find_ok = True 271 break 272 if find_ok is False: 273 SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': rtt_pre_inc_item}) 274 if len(compile_include_files_options) == 0: 275 for option in compile_defs_options: 276 defs = option.findall('listOptionValue') 277 project_defs = [] 278 for item in defs: 279 if reset is True: 280 # clean all old configuration 281 option.remove(item) 282 else: 283 project_defs += [item.get('value')] 284 if len(project_defs) > 0: 285 cproject_defs = set(CPPDEFINES) - set(project_defs) 286 else: 287 cproject_defs = CPPDEFINES 288 289 # print('c.compiler.defs') 290 cproject_defs = sorted(cproject_defs) 291 for item in cproject_defs: 292 SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': item}) 293 294 # update linker script config 295 if linker_scriptfile_option is not None : 296 option = linker_scriptfile_option 297 linker_script = 'link.lds' 298 items = env['LINKFLAGS'].split(' ') 299 if '-T' in items: 300 linker_script = items[items.index('-T') + 1] 301 linker_script = ConverToRttEclipsePathFormat(linker_script) 302 303 listOptionValue = option.find('listOptionValue') 304 if listOptionValue != None: 305 if reset is True or IsRttEclipsePathFormat(listOptionValue.get('value')): 306 listOptionValue.set('value', linker_script) 307 else: 308 SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': linker_script}) 309 # scriptfile in stm32cubeIDE 310 if linker_script_option is not None : 311 option = linker_script_option 312 items = env['LINKFLAGS'].split(' ') 313 if '-T' in items: 314 linker_script = ConverToRttEclipsePathFormat(items[items.index('-T') + 1]).strip('"') 315 option.set('value', linker_script) 316 # update nostartfiles config 317 if linker_nostart_option is not None : 318 option = linker_nostart_option 319 if env['LINKFLAGS'].find('-nostartfiles') != -1: 320 option.set('value', 'true') 321 else: 322 option.set('value', 'false') 323 # update libs 324 if linker_libs_option is not None: 325 option = linker_libs_option 326 # remove old libs 327 for item in option.findall('listOptionValue'): 328 if IsRttEclipseLibFormat(item.get("value")): 329 option.remove(item) 330 331 # add new libs 332 if 'LIBS' in env: 333 for lib in env['LIBS']: 334 lib_name = os.path.basename(str(lib)) 335 if lib_name.endswith('.a'): 336 if lib_name.startswith('lib'): 337 lib = lib_name[3:].split('.')[0] 338 else: 339 lib = ':' + lib_name 340 formatedLib = ConverToRttEclipseLibFormat(lib) 341 SubElement(option, 'listOptionValue', { 342 'builtIn': 'false', 'value': formatedLib}) 343 344 # update lib paths 345 if linker_paths_option is not None: 346 option = linker_paths_option 347 # remove old lib paths 348 for item in option.findall('listOptionValue'): 349 if IsRttEclipsePathFormat(item.get('value')): 350 # clean old configuration 351 option.remove(item) 352 # add new old lib paths 353 for path in env['LIBPATH']: 354 SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': ConverToRttEclipsePathFormat(RelativeProjectPath(env, path).replace('\\', '/'))}) 355 356 return 357 358 359def 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 382 383 out = open('.project', 'w') 384 out.write('<?xml version="1.0" encoding="UTF-8"?>\n') 385 xml_indent(root) 386 out.write(etree.tostring(root, encoding='utf-8').decode('utf-8')) 387 out.close() 388 389 return 390 391 392def GenExcluding(env, project): 393 rtt_root = os.path.abspath(env['RTT_ROOT']) 394 bsp_root = os.path.abspath(env['BSP_ROOT']) 395 coll_dirs = CollectPaths(project['DIRS']) 396 all_paths_temp = [OSPath(path) for path in coll_dirs] 397 all_paths = [] 398 399 # add used path 400 for path in all_paths_temp: 401 if path.startswith(rtt_root) or path.startswith(bsp_root): 402 all_paths.append(path) 403 404 if bsp_root.startswith(rtt_root): 405 # bsp folder is in the RT-Thread root folder, such as the RT-Thread source code on GitHub 406 exclude_paths = ExcludePaths(rtt_root, all_paths) 407 elif rtt_root.startswith(bsp_root): 408 # RT-Thread root folder is in the bsp folder, such as project folder which generate by 'scons --dist' cmd 409 check_path = [] 410 exclude_paths = [] 411 # analyze the primary folder which relative to BSP_ROOT and in all_paths 412 for path in all_paths: 413 if path.startswith(bsp_root): 414 folders = RelativeProjectPath(env, path).split('\\') 415 if folders[0] != '.' and '\\' + folders[0] not in check_path: 416 check_path += ['\\' + folders[0]] 417 # exclue the folder which has managed by scons 418 for path in check_path: 419 exclude_paths += ExcludePaths(bsp_root + path, all_paths) 420 else: 421 exclude_paths = ExcludePaths(rtt_root, all_paths) 422 exclude_paths += ExcludePaths(bsp_root, all_paths) 423 424 paths = exclude_paths 425 exclude_paths = [] 426 # remove the folder which not has source code by source_pattern 427 for path in paths: 428 # add bsp and libcpu folder and not collect source files (too more files) 429 if path.endswith('rt-thread\\bsp') or path.endswith('rt-thread\\libcpu'): 430 exclude_paths += [path] 431 continue 432 433 set = CollectAllFilesinPath(path, source_pattern) 434 if len(set): 435 exclude_paths += [path] 436 437 exclude_paths = [RelativeProjectPath(env, path).replace('\\', '/') for path in exclude_paths] 438 439 all_files = CollectFiles(all_paths, source_pattern) 440 src_files = project['FILES'] 441 442 exclude_files = ExcludeFiles(all_files, src_files) 443 exclude_files = [RelativeProjectPath(env, file).replace('\\', '/') for file in exclude_files] 444 445 env['ExPaths'] = exclude_paths 446 env['ExFiles'] = exclude_files 447 448 return exclude_paths + exclude_files 449 450 451def RelativeProjectPath(env, path): 452 project_root = os.path.abspath(env['BSP_ROOT']) 453 rtt_root = os.path.abspath(env['RTT_ROOT']) 454 455 if path.startswith(project_root): 456 return _make_path_relative(project_root, path) 457 458 if path.startswith(rtt_root): 459 return 'rt-thread/' + _make_path_relative(rtt_root, path) 460 461 # TODO add others folder 462 print('ERROR: the ' + path + ' not support') 463 464 return path 465 466 467def HandleExcludingOption(entry, sourceEntries, excluding): 468 old_excluding = [] 469 if entry != None: 470 exclud = entry.get('excluding') 471 if exclud != None: 472 old_excluding = entry.get('excluding').split('|') 473 sourceEntries.remove(entry) 474 475 value = '' 476 for item in old_excluding: 477 if item.startswith('//'): 478 old_excluding.remove(item) 479 else: 480 if value == '': 481 value = item 482 else: 483 value += '|' + item 484 485 for item in excluding: 486 # add special excluding path prefix for RT-Thread 487 item = '//' + item 488 if value == '': 489 value = item 490 else: 491 value += '|' + item 492 493 SubElement(sourceEntries, 'entry', {'excluding': value, 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', 'kind':'sourcePath', 'name':""}) 494 495 496def UpdateCproject(env, project, excluding, reset, prj_name): 497 excluding = sorted(excluding) 498 499 cproject = etree.parse('.cproject') 500 501 root = cproject.getroot() 502 cconfigurations = root.findall('storageModule/cconfiguration') 503 for cconfiguration in cconfigurations: 504 tools = cconfiguration.findall('storageModule/configuration/folderInfo/toolChain/tool') 505 HandleToolOption(tools, env, project, reset) 506 507 sourceEntries = cconfiguration.find('storageModule/configuration/sourceEntries') 508 if sourceEntries != None: 509 entry = sourceEntries.find('entry') 510 HandleExcludingOption(entry, sourceEntries, excluding) 511 # update refreshScope 512 if prj_name: 513 prj_name = '/' + prj_name 514 configurations = root.findall('storageModule/configuration') 515 for configuration in configurations: 516 resource = configuration.find('resource') 517 configuration.remove(resource) 518 SubElement(configuration, 'resource', {'resourceType': "PROJECT", 'workspacePath': prj_name}) 519 520 # write back to .cproject 521 out = open('.cproject', 'w') 522 out.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n') 523 out.write('<?fileVersion 4.0.0?>') 524 xml_indent(root) 525 out.write(etree.tostring(root, encoding='utf-8').decode('utf-8')) 526 out.close() 527 528 529def TargetEclipse(env, reset=False, prj_name=None): 530 global source_pattern 531 532 print('Update eclipse setting...') 533 534 # generate cproject file 535 if not os.path.exists('.cproject'): 536 if rt_studio.gen_cproject_file(os.path.abspath(".cproject")) is False: 537 print('Fail!') 538 return 539 540 # generate project file 541 if not os.path.exists('.project'): 542 if rt_studio.gen_project_file(os.path.abspath(".project")) is False: 543 print('Fail!') 544 return 545 546 # generate projcfg.ini file 547 if not os.path.exists('.settings/projcfg.ini'): 548 # if search files with uvprojx or uvproj suffix 549 file = "" 550 items = os.listdir(".") 551 if len(items) > 0: 552 for item in items: 553 if item.endswith(".uvprojx") or item.endswith(".uvproj"): 554 file = os.path.abspath(item) 555 break 556 chip_name = rt_studio.get_mcu_info(file) 557 if rt_studio.gen_projcfg_ini_file(chip_name, prj_name, os.path.abspath(".settings/projcfg.ini")) is False: 558 print('Fail!') 559 return 560 561 # enable lowwer .s file compiled in eclipse cdt 562 if not os.path.exists('.settings/org.eclipse.core.runtime.prefs'): 563 if rt_studio.gen_org_eclipse_core_runtime_prefs( 564 os.path.abspath(".settings/org.eclipse.core.runtime.prefs")) is False: 565 print('Fail!') 566 return 567 568 # add clean2 target to fix issues when files too many 569 if not os.path.exists('makefile.targets'): 570 if rt_studio.gen_makefile_targets(os.path.abspath("makefile.targets")) is False: 571 print('Fail!') 572 return 573 574 project = ProjectInfo(env) 575 576 # update the project file structure info on '.project' file 577 UpdateProjectStructure(env, prj_name) 578 579 # generate the exclude paths and files 580 excluding = GenExcluding(env, project) 581 582 # update the project configuration on '.cproject' file 583 UpdateCproject(env, project, excluding, reset, prj_name) 584 585 print('done!') 586 587 return 588