twister: Better manage gcov sym link

In twister coverage runs we create the gcov sym link to llvm-cov
executable. This code runs multiple times which causes errors with the
current implementation since creating the symlink will fail; then
copying will also fail because we're using the symlink (self copy the
file).

Add a helper function to `try_making_symlink` which will handle the
following cases:
1. If the symlink already exists and is correct, do nothing.
2. If the symlink exists but points to the wrong file, replace it.
3. If the symlink doesn't exist create it.
4. If creating a symlink fails, just copy the file.

Signed-off-by: Yuval Peress <peress@google.com>
This commit is contained in:
Yuval Peress 2025-05-05 15:16:39 +00:00 committed by Benjamin Cabé
commit 5b19a9b94d

View file

@ -4,6 +4,7 @@
# SPDX-License-Identifier: Apache-2.0
import contextlib
import filecmp
import glob
import logging
import os
@ -502,6 +503,45 @@ class Gcovr(CoverageTool):
return ret, { 'report': coverage_file, 'ztest': ztest_file, 'summary': coverage_summary }
def try_making_symlink(source: str, link: str):
"""
Attempts to create a symbolic link from source to link.
If the link already exists:
- If it's a symlink pointing to a different source, it's replaced.
- If it's a regular file with the same content, no action is taken.
- If it's a regular file with different content, it's replaced with a
symlink (if possible, otherwise a copy).
If symlinking fails for any reason (other than the link already existing and
being correct), it attempts to copy the source to the link.
Args:
source (str): The path to the source file.
link (str): The path where the symbolic link should be created.
"""
if os.path.exists(link):
if os.path.islink(link):
if os.readlink(link) == source:
# Link is already set up
return
# Link is pointing to the wrong file, fall below this if/else and
# it will be replaced
elif filecmp.cmp(source, link):
# File contents are the same
return
# link exists, but points to a different file, remove the link. We'll
# try to create a new one below
os.remove(link)
# Create the symlink
try:
os.symlink(source, link)
except OSError as e:
logger.error(
"Error creating symlink: %s, attempting to copy.". str(e)
)
shutil.copy(source, link)
def choose_gcov_tool(options, is_system_gcov):
gcov_tool = None
@ -516,10 +556,7 @@ def choose_gcov_tool(options, is_system_gcov):
llvm_cov = shutil.which("llvm-cov", path=llvm_path)
llvm_cov_ext = pathlib.Path(llvm_cov).suffix
gcov_lnk = os.path.join(options.outdir, f"gcov{llvm_cov_ext}")
try:
os.symlink(llvm_cov, gcov_lnk)
except OSError:
shutil.copy(llvm_cov, gcov_lnk)
try_making_symlink(llvm_cov, gcov_lnk)
gcov_tool = gcov_lnk
elif is_system_gcov:
gcov_tool = "gcov"