mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
fix(ldgen): add the linker template to the generation fingerprint
ldgen skips regeneration when a fingerprint over the section names, the linker fragment files, sdkconfig and Kconfig is unchanged. The linker script template was not part of that fingerprint, even though everything outside its mapping placeholders is copied into the generated script verbatim. The build system regenerates the preprocessed template whenever any of its inputs changes, not only when a linker fragment changes. When such a change reaches ldgen but leaves the section names alone, the fingerprint still matches, generation is skipped, and the image is linked against the previously generated script. The skip path updates the mtime of the output, so nothing in the build output shows that the change was dropped. Add the template to the fingerprint so that regenerating it always regenerates the linker script. Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
@@ -27,7 +27,7 @@ from pyparsing import ParseFatalException
|
|||||||
_RE_SECTION_NAME = re.compile(r'^\s*\d+\s+(\.\S+)', re.MULTILINE)
|
_RE_SECTION_NAME = re.compile(r'^\s*\d+\s+(\.\S+)', re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
def _compute_fingerprint(sections_infos, fragment_files, config_file, kconfig_file):
|
def _compute_fingerprint(sections_infos, fragment_files, config_file, kconfig_file, input_file):
|
||||||
"""Compute a fingerprint from section names and mtimes of all inputs."""
|
"""Compute a fingerprint from section names and mtimes of all inputs."""
|
||||||
hasher = hashlib.md5()
|
hasher = hashlib.md5()
|
||||||
|
|
||||||
@@ -36,9 +36,12 @@ def _compute_fingerprint(sections_infos, fragment_files, config_file, kconfig_fi
|
|||||||
names = _RE_SECTION_NAME.findall(info.content)
|
names = _RE_SECTION_NAME.findall(info.content)
|
||||||
hasher.update((archive + ':' + ','.join(names)).encode())
|
hasher.update((archive + ':' + ','.join(names)).encode())
|
||||||
|
|
||||||
# Mtimes of fragment files, sdkconfig, kconfig
|
# Mtimes of fragment files, the linker template, sdkconfig, kconfig. The
|
||||||
|
# template counts as an input because everything outside its mapping
|
||||||
|
# placeholders ends up in the generated script verbatim, so an edit to it
|
||||||
|
# changes the output even when no section name does.
|
||||||
input_files = [p.name if hasattr(p, 'name') else p for p in fragment_files]
|
input_files = [p.name if hasattr(p, 'name') else p for p in fragment_files]
|
||||||
input_files += [p for p in (config_file, kconfig_file) if p]
|
input_files += [p.name if hasattr(p, 'name') else p for p in (input_file, config_file, kconfig_file) if p]
|
||||||
for path in input_files:
|
for path in input_files:
|
||||||
try:
|
try:
|
||||||
hasher.update(f'{path}:{os.path.getmtime(path)}'.encode())
|
hasher.update(f'{path}:{os.path.getmtime(path)}'.encode())
|
||||||
@@ -228,7 +231,9 @@ def main():
|
|||||||
# Check if we can skip generation entirely — section names and other
|
# Check if we can skip generation entirely — section names and other
|
||||||
# inputs unchanged since last run.
|
# inputs unchanged since last run.
|
||||||
fingerprint = (
|
fingerprint = (
|
||||||
None if no_cache else _compute_fingerprint(sections_infos, fragment_files, config_file, kconfig_file)
|
None
|
||||||
|
if no_cache
|
||||||
|
else _compute_fingerprint(sections_infos, fragment_files, config_file, kconfig_file, input_file)
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
output_path
|
output_path
|
||||||
|
|||||||
@@ -174,6 +174,32 @@ def test_rebuild_ldgen_fingerprint(idf_py: IdfPyFunc, test_app_copy: Path) -> No
|
|||||||
assert skip_msg not in result.stdout, f'unexpected {skip_msg!r} in build output after fragment change'
|
assert skip_msg not in result.stdout, f'unexpected {skip_msg!r} in build output after fragment change'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('idf_copy')
|
||||||
|
def test_rebuild_ldgen_template_change(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||||
|
"""Verify that a change to the linker script template reaches the generated
|
||||||
|
script. ldgen copies everything outside the mapping placeholders from the
|
||||||
|
template into its output verbatim, so the template is an input in its own
|
||||||
|
right and must not be hidden behind the fingerprint skip.
|
||||||
|
"""
|
||||||
|
skip_msg = 'Skipping linker script generation, section names unchanged'
|
||||||
|
idf_path = Path(os.environ['IDF_PATH'])
|
||||||
|
template = idf_path / 'components/esp_system/ld/esp32/sections.ld.in'
|
||||||
|
generated = Path('build/esp-idf/esp_system/ld/sections.ld')
|
||||||
|
# An absolute symbol assignment: it is copied through to the generated
|
||||||
|
# script and does not depend on anything else in the build.
|
||||||
|
probe = '_ldgen_template_probe'
|
||||||
|
|
||||||
|
logging.info('initial build')
|
||||||
|
idf_py('build')
|
||||||
|
|
||||||
|
logging.info('adding a symbol to the template leaves section names and fragment files unchanged')
|
||||||
|
with open(template, 'a') as f:
|
||||||
|
f.write(f'\n{probe} = 0x3ff00abc;\n')
|
||||||
|
result = idf_py('build')
|
||||||
|
assert skip_msg not in result.stdout, f'unexpected {skip_msg!r} in build output after template change'
|
||||||
|
assert probe in generated.read_text(), 'template change did not reach the generated linker script'
|
||||||
|
|
||||||
|
|
||||||
def test_rebuild_ldgen_lf_cache(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
def test_rebuild_ldgen_lf_cache(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||||
"""Verify the ldgen lf cache: when section names change but fragment files
|
"""Verify the ldgen lf cache: when section names change but fragment files
|
||||||
don't, parsed FragmentFile objects are loaded from cache rather than
|
don't, parsed FragmentFile objects are loaded from cache rather than
|
||||||
|
|||||||
Reference in New Issue
Block a user