Skip to content

Commit

Permalink
Merge branch 'mr/leger/master/fix-mingw-paths-in-dll-closure-check' i…
Browse files Browse the repository at this point in the history
…nto 'master'

Fix mingw paths on dll closure check

Closes #36

See merge request it/e3-core!93
  • Loading branch information
grouigrokon committed Jan 21, 2025
2 parents 8a9dc52 + 7add3ed commit beb7162
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 15 deletions.
28 changes: 17 additions & 11 deletions src/e3/anod/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from e3.anod.qualifiers_manager import QualifiersManager
from e3.anod.qualifier import Qualifier
from e3.fs import find
from e3.os.fs import which
from e3.os.fs import ldd_output_to_posix, which
from e3.platform_db.knowledge_base import OS_INFO
from e3.yaml import load_with_config

Expand Down Expand Up @@ -474,17 +474,23 @@ def check_shared_libraries_closure(
prefix = self.build_space.install_dir

# Get all files matching the OS shared lib extension.
lib_files = find(
root=prefix, pattern=f"*{OS_INFO[e3.env.Env().build.os.name]['dllext']}"
)
ldd_output = e3.os.process.Run(["ldd"] + lib_files).out or ""

# When only one path is specified, ldd does not add the name of the
# file in the output. This would break the parsing below.
if len(lib_files) == 1:
ldd_output = f"{lib_files[0]}:\n{ldd_output}"
shlib_ext: str = f"{OS_INFO[e3.env.Env().build.os.name]['dllext']}"
lib_files = find(root=prefix, pattern=f"*{shlib_ext}")

if not lib_files:
self.log.info(f"No {shlib_ext} files to check in {prefix}")
ldd_output = ""
else:
ldd_output = e3.os.process.Run(["ldd"] + lib_files).out or ""

# When only one path is specified, ldd does not add the name of the
# file in the output. This would break the parsing below.
if len(lib_files) == 1:
ldd_output = f"{lib_files[0]}:\n{ldd_output}"
ldd_output = ldd_output_to_posix(ldd_output)
else:
# An ldd output has been provided.
# An ldd output has been provided. Update it first to Posix.
ldd_output = ldd_output_to_posix(ldd_output)
lib_files = re.findall(r"^([^\t].*):$", ldd_output, flags=re.M)

if self.sandbox and hasattr(self.sandbox, "root_dir"):
Expand Down
61 changes: 58 additions & 3 deletions src/e3/os/fs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Low-level file manipulation.
All function here should be platform indepenent, should not involve globbing or
All function here should be platform independent, should not involve globbing or
logging (unless in case of unexpected failure).
"""

Expand All @@ -15,16 +15,15 @@
import sys


from pathlib import Path
from typing import TYPE_CHECKING, overload

import e3
import e3.error
import e3.log

if TYPE_CHECKING:
from typing import Any, Literal
from collections.abc import Callable
from pathlib import Path


class OSFSError(e3.error.E3Error):
Expand Down Expand Up @@ -248,6 +247,62 @@ def force_remove_file(path: str | Path) -> None:
os.chmod(dir_path, orig_mode)


def ldd_output_to_posix(ldd_output: str) -> str:
"""Transform an ``ldd`` output to POSIX paths only.
This method does not have any impact when the ``ldd`` output has
been executed on a Unix host, because paths are already POSIX there.
It applies only to ``ldd`` outputs on Windows, where the paths are
transformed by a call to ``cygpath -m``.
For instance, ``/c/WINDOWS/System32/ntdll.dll`` is transformed to
``C:/WINDOWS/System32/ntdll.dll``.
.. note:: The transformation is made on strings only to minimize the
call the ``cygpath``. If a file path is found, all its occurrences
are replaced by its POSIX value.
:param ldd_output: The output of an ``ldd`` call to transform to contain
only POSIX paths.
:return: An ``ldd`` output with POSIX paths only.
"""
# Module may not be imported at toplevel, as it depends on this module.
from e3.os.process import Run

posix_content: str = ldd_output

if sys.platform == "win32" and Path(which("cygpath")).exists():
transformed: dict[str, str] = {}
posix_path: str
file_path: str | None = None
lines: list[str] = posix_content.splitlines()

for line in lines:
if line.strip().endswith(":"):
# Found an executable/dll path with linked dlls.
file_path = line.strip()[:-1]
elif " => " in line:
# Here is the list of linked dlls.
file_path = line.split(" => ", 1)[1].strip()

# Do not run on already transformed paths.
if file_path and file_path not in transformed:
cygpath_run: Run = Run(["cygpath", "-m", file_path])

# The value returned by cygpath may contain a trailing "\n",
# better remove it.
posix_path = cygpath_run.out.strip() if cygpath_run.out else file_path

if posix_path != file_path:
# Transform all occurrences in the ldd output.
posix_content = posix_content.replace(file_path, posix_path)
transformed[file_path] = posix_path

return posix_content


def max_path() -> int:
"""Return the maximum length for a path.
Expand Down
3 changes: 2 additions & 1 deletion tests/tests_e3/anod/spec_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
),
(
(
"- KERNEL32.DLL: /Windows/System32/KERNEL32.DLL"
"- KERNEL32.DLL: C:/Windows/System32/KERNEL32.DLL"
if sys.platform == "win32"
else None
),
Expand Down Expand Up @@ -190,6 +190,7 @@ def test_spec_check_dll_closure(ldd, arguments: tuple, expected: tuple) -> None:
(errors,) = expected
test_spec: Anod = Anod("", kind="install")
test_spec.sandbox = SandBox(root_dir=os.getcwd())

if ldd_output is None:
# Use the current executable lib directory.
exe_path: Path = Path(sys.executable)
Expand Down
13 changes: 13 additions & 0 deletions tests/tests_e3/os/fs/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import e3.fs
import e3.os.fs
import e3.os.process

import pytest

Expand Down Expand Up @@ -120,6 +121,18 @@ def test_df():
assert all(isinstance(elt, numbers.Integral) for elt in statfs)


def test_anod_ldd_output_to_posix() -> None: # type: ignore[no-untyped-def]
# Get the ldd output of the current executable.
ldd_output = e3.os.process.Run(["ldd"] + [sys.executable]).out or ""
e3.os.fs.ldd_output_to_posix(ldd_output)
# Give several files to ldd so that the file names are also covered by the
# test (not only the dll files)
ldd_output = (
e3.os.process.Run(["ldd"] + [sys.executable, e3.os.fs.which("ldd")]).out or ""
)
e3.os.fs.ldd_output_to_posix(ldd_output)


def test_maxpath():
maxPath = e3.os.fs.max_path()
assert isinstance(maxPath, numbers.Integral)
Expand Down

0 comments on commit beb7162

Please sign in to comment.