Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 151 additions & 2 deletions tools/targets/eclipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,151 @@ def HandleExcludingOption(entry, sourceEntries, excluding):
SubElement(sourceEntries, 'entry', {'excluding': value, 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', 'kind':'sourcePath', 'name':""})


def UpdateCproject(env, project, excluding, reset, prj_name):
def CollectLocalDefines(env):
"""
Collect the per-group LOCAL_CPPDEFINES and map them to the group source files.

Returns a dict: relative project path (str) -> list of define strings.
The Eclipse project generator only writes global CPPDEFINES into
rtconfig_preinc.h; LOCAL_CPPDEFINES must be attached to the corresponding
source files as per-file defined symbols (CDT fileInfo), so that the macro
scope stays unchanged and other source files are not affected.
"""
local_defines = {}
groups = env.get('project', [])
if not groups:
return local_defines

for group in groups:
if 'LOCAL_CPPDEFINES' not in group:
continue
defines = []
for item in group['LOCAL_CPPDEFINES']:
item = str(item).strip()
if item and item not in defines:
defines.append(item)
if not defines:
continue

src = group.get('src')
if not src:
continue

for f in src:
rp = RelativeProjectPath(env, os.path.abspath(str(f))).replace('\\', '/')
if rp.startswith('.'):
continue
cur = local_defines.setdefault(rp, [])
for d in defines:
if d not in cur:
cur.append(d)

return local_defines


# marker used in generated per-file option ids, so they can be refreshed
# (removed and regenerated) on every 'scons --target=eclipse' run
_rtt_local_marker = '.rttlocal.'


def _IsCppFile(rp):
return rp.endswith(('.cpp', '.cc', '.cxx', '.C'))


def _GetCompilerInstanceId(configuration, rp):
"""
Return the actual compiler tool instance id that owns the source file.

CDT file-level <tool> nodes must reference an existing per-folder/toolchain
compiler *instance* (superClass = instance id), not the base plug-in class:
only then does the file-level tool inherit the options that
HandleToolOption() writes on the folder/toolchain instance (include paths,
'-include rtconfig_preinc.h', global defines, ...). Using the base class
would leave kernel sources without the header search paths / preinclude
and break the Studio build.
"""
def _first_compiler_tool(container):
for tool in container.findall('tool'):
sid = tool.get('superClass', '')
if 'tool.cpp.compiler' in sid:
if _IsCppFile(rp):
return tool.get('id')
elif 'tool.c.compiler' in sid and not _IsCppFile(rp):
return tool.get('id')
return None

# per-folder compiler instances: relative folder path -> tool instance id
# NOTE: match by 'resourcePath' (the real project-relative folder path),
# NOT by 'name'. CDT 'name' is a display name that is often just '/'
# even for sub-folder configurations (e.g. a root config with
# name="/" resourcePath="" and a sub-folder config with name="/"
# resourcePath="rt-thread/src"), so 'name' cannot distinguish folders
# and the longest-prefix match would always fall back to the root one,
# skipping the sub-folder compiler's custom options.
folders = []
for fi in configuration.findall('folderInfo'):
d = fi.get('resourcePath', '').strip().lstrip('/')
tid = None
tc = fi.find('toolChain')
if tc is not None:
tid = _first_compiler_tool(tc)
if tid is None:
tid = _first_compiler_tool(fi)
if tid is not None:
folders.append((d, tid))

# pick the folder with the longest matching directory prefix;
# covers root ('', matches everything), nested folders and files
# outside any configured folder (they fall back to the root one)
best = None
for d, tid in folders:
matched = (d == '') or rp == d or rp.startswith(d + '/')
if matched and (best is None or len(d) > len(best[0])):
best = (d, tid)
if best is not None:
return best[1]

# fallback: toolchain-level compiler instance
tc = configuration.find('toolChain')
if tc is not None:
tid = _first_compiler_tool(tc)
if tid is not None:
return tid
return None


def UpdateLocalDefinesFileInfo(configuration, ccfg_id, local_defines):
"""
Generate/refresh CDT per-file <fileInfo> nodes that add the group's
LOCAL_CPPDEFINES as defined symbols on the corresponding source files.
"""
# remove previously generated fileInfo nodes
for fi in configuration.findall('fileInfo'):
if _rtt_local_marker in fi.get('id', ''):
configuration.remove(fi)

for rp, defines in local_defines.items():
fi_id = ccfg_id + _rtt_local_marker + rp.replace('/', '.').strip('.')
# 'resourcePath' is required by CDT to associate the file (getPath());
# 'name' alone cannot replace it, otherwise the project configuration
# fails to load (addResourceConfiguration -> getPath().segmentCount()).
fi = SubElement(configuration, 'fileInfo',
{'id': fi_id, 'name': '/' + rp, 'resourcePath': rp})
Comment on lines +647 to +648

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 复用已有文件级配置,保留用户设置

如果用户已为 clock.c 设置文件级编译选项,例如 -fno-builtin,这里仍会追加一个相同 resourcePath 的 fileInfo。CDT 按路径保存配置,后加载的新节点会覆盖原节点,原有选项随之失效。

另外,用户在本函数生成的节点上通过 Studio 修改选项后,下次导出又会在第 638–640 行将整个节点删除,这些修改也会丢失。

请按 resourcePath 查找并复用已有节点,只更新导出器管理的宏,保留用户的其他选项。首次导出和重复导出都需要覆盖这两种情况。

# inherit the real compiler instance so folder/toolchain options
# (-I, '-include rtconfig_preinc.h', global defines) are kept
compiler_id = _GetCompilerInstanceId(configuration, rp)
if compiler_id is None:
compiler_id = 'ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler'
tool = SubElement(fi, 'tool', {'id': fi_id + '.tool',
'superClass': compiler_id})
option = SubElement(tool, 'option', {'id': fi_id + '.defs',
'superClass': 'ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.defs',
'useByScannerDiscovery': 'true'})
for d in defines:
SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': d})
Comment on lines +656 to +660

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 添加局部宏时保留原有宏列表

如果用户在 Studio 的工程或目录属性中配置了宏,这里新建的 compiler.defs 会替换继承的宏列表。例如目录配置已有 FOLDER_FEATURE=1,给 clock.c 添加局部宏后,该文件的有效列表只剩 __RT_KERNEL_SOURCE__。这些由用户配置、未写入 rtconfig_preinc.h 的宏会从编译命令中消失,可能导致编译失败或改变条件编译结果。

请先取得该文件原本有效的宏列表,再合并 LOCAL_CPPDEFINES,并验证原有宏和新增宏都保留在实际编译命令中。



def UpdateCproject(env, project, excluding, reset, prj_name, local_defines=None):
excluding = sorted(excluding)

cproject = etree.parse('.cproject')
Expand All @@ -527,6 +671,11 @@ def UpdateCproject(env, project, excluding, reset, prj_name):
tools = cconfiguration.findall('storageModule/configuration/folderInfo/toolChain/tool')
HandleToolOption(tools, env, project, reset)

# per-file LOCAL_CPPDEFINES: attach them to the corresponding source files
cfg = cconfiguration.find('storageModule[@moduleId="cdtBuildSystem"]/configuration')
if cfg is not None:
UpdateLocalDefinesFileInfo(cfg, cconfiguration.get('id'), local_defines or {})

sourceEntries = cconfiguration.find('storageModule/configuration/sourceEntries')
if sourceEntries != None:
entry = sourceEntries.find('entry')
Expand Down Expand Up @@ -603,7 +752,7 @@ def TargetEclipse(env, reset=False, prj_name=None):
excluding = GenExcluding(env, project)

# update the project configuration on '.cproject' file
UpdateCproject(env, project, excluding, reset, prj_name)
UpdateCproject(env, project, excluding, reset, prj_name, CollectLocalDefines(env))

print('done!')

Expand Down
Loading