Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: work with lief disabled or uninstalled #2777

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
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
73 changes: 43 additions & 30 deletions cx_Freeze/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,14 @@
from pathlib import Path
from tempfile import TemporaryDirectory

from cx_Freeze._compat import IS_MINGW, IS_WINDOWS, PLATFORM
from cx_Freeze._compat import PLATFORM
from cx_Freeze.exception import PlatformError

# In Windows, to get dependencies, the default is to use lief package,
# but LIEF can be disabled with:
# set CX_FREEZE_BIND=imagehlp
if IS_WINDOWS or IS_MINGW:
import lief

with suppress(ImportError):
from .util import BindError, GetDependentFiles
try:
# LIEF 0.15+
lief.logging.set_level(lief.logging.LEVEL.ERROR)
except AttributeError:
lief.logging.set_level(lief.logging.LOGGING_LEVEL.ERROR)

LIEF_DISABLED = os.environ.get("CX_FREEZE_BIND", "") == "imagehlp"

# In Linux, to get dependencies, the default is to use ldd in x64 platforms
LDD_DISABLED = (
os.environ.get(
"CX_FREEZE_BIND", "" if PLATFORM.endswith("x86_64") else "patchelf"
Expand Down Expand Up @@ -119,8 +109,21 @@ class PEParser(Parser):
def __init__(
self, path: list[str], bin_path_includes: list[str], silent: int = 0
) -> None:
if os.environ.get("CX_FREEZE_BIND", "") == "imagehlp":
lief = None
else:
try:
import lief
except ImportError:
lief = None
else:
try:
# LIEF 0.15+
lief.logging.set_level(lief.logging.LEVEL.ERROR)
except AttributeError:
lief.logging.set_level(lief.logging.LOGGING_LEVEL.ERROR)
super().__init__(path, bin_path_includes, silent)
if hasattr(lief.PE, "ParserConfig"):
if lief and hasattr(lief.PE, "ParserConfig"):
# LIEF 0.14+
imports_only = lief.PE.ParserConfig()
imports_only.parse_exports = False
Expand All @@ -139,6 +142,15 @@ def __init__(
else:
self.imports_only = None
self.resource_only = None
if lief:
self._pe = lief.PE
else:
from cx_Freeze.util import BindError, GetDependentFiles

self.GetDependentFiles = GetDependentFiles
self.BindError = BindError
self._get_dependent_files = self._get_dependent_files_imagehlp
self._pe = None

@staticmethod
def is_pe(filename: str | Path) -> bool:
Expand All @@ -149,9 +161,9 @@ def is_pe(filename: str | Path) -> bool:

_is_binary = is_pe

def _get_dependent_files_lief(self, filename: Path) -> set[Path]:
def _get_dependent_files(self, filename: Path) -> set[Path]:
with filename.open("rb", buffering=0) as raw:
binary = lief.PE.parse(raw, self.imports_only or filename.name)
binary = self._pe.parse(raw, self.imports_only or filename.name)
if not binary:
return set()

Expand Down Expand Up @@ -179,8 +191,8 @@ def _get_dependent_files_imagehlp(self, filename: Path) -> set[Path]:
[os.path.normpath(path) for path in self.search_path]
)
try:
return {Path(dep) for dep in GetDependentFiles(filename)}
except BindError as exc:
return {Path(dep) for dep in self.GetDependentFiles(filename)}
except self.BindError as exc:
# Sometimes this gets called when filename is not actually
# a library (See issue 88).
if self._silent < 3:
Expand All @@ -190,20 +202,18 @@ def _get_dependent_files_imagehlp(self, filename: Path) -> set[Path]:
os.environ["PATH"] = env_path
return set()

if LIEF_DISABLED:
_get_dependent_files = _get_dependent_files_imagehlp
else:
_get_dependent_files = _get_dependent_files_lief

def read_manifest(self, filename: str | Path) -> str:
""":return: the XML schema of the manifest included in the executable
:rtype: str

"""
if isinstance(filename, str):
filename = Path(filename)
if self._pe is None:
if self._silent < 3:
print("WARNING: ignoring read manifest for {filename}")
return ""
filename = Path(filename)
with filename.open("rb", buffering=0) as raw:
binary = lief.PE.parse(raw, self.resource_only or filename.name)
binary = self._pe.parse(raw, self.resource_only or filename.name)
resources_manager = binary.resources_manager
return (
resources_manager.manifest
Expand All @@ -216,13 +226,16 @@ def write_manifest(self, filename: str | Path, manifest: str) -> None:
:rtype: str

"""
if isinstance(filename, str):
filename = Path(filename)
if self._pe is None:
if self._silent < 3:
print("WARNING: ignoring write manifest for {filename}")
return
filename = Path(filename)
with filename.open("rb", buffering=0) as raw:
binary = lief.PE.parse(raw, self.resource_only or filename.name)
binary = self._pe.parse(raw, self.resource_only or filename.name)
resources_manager = binary.resources_manager
resources_manager.manifest = manifest
builder = lief.PE.Builder(binary)
builder = self._pe.Builder(binary)
builder.build_resources(True)
builder.build()
with TemporaryDirectory(prefix="cxfreeze-") as tmp_dir:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_executables.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ def test_not_found_icon(tmp_path: Path) -> None:
"""


@pytest.mark.skipif(sys.platform != "win32", reason="Windows tests")
@pytest.mark.skipif(not (IS_MINGW or IS_WINDOWS), reason="Windows tests")
def test_invalid_icon(tmp_path: Path) -> None:
"""Test with invalid icon in Windows."""
create_package(tmp_path, SOURCE_INVALID_ICON)
Expand Down
17 changes: 15 additions & 2 deletions tests/test_windows_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from __future__ import annotations

import ctypes
import os
import sys
from typing import TYPE_CHECKING

import pytest
from generate_samples import create_package, run_command

from cx_Freeze._compat import PLATFORM, PYTHON_VERSION
from cx_Freeze._compat import BUILD_EXE_DIR
from cx_Freeze.parser import PEParser

if TYPE_CHECKING:
Expand Down Expand Up @@ -72,7 +73,7 @@ def tmp_manifest(tmp_path_factory) -> Path:
tmp_path = tmp_path_factory.mktemp("manifest")
create_package(tmp_path, SOURCE)
run_command(tmp_path)
return tmp_path / f"build/exe.{PLATFORM}-{PYTHON_VERSION}"
return tmp_path / BUILD_EXE_DIR


def test_manifest(tmp_manifest: Path) -> None:
Expand All @@ -89,6 +90,10 @@ def test_simple_manifest(tmp_manifest: Path) -> None:
"""With simple manifest, without "supportedOS Id", windows version returned
is the compatible version for Windows 8.1, ie, 6.2.
"""
if os.environ.get("CX_FREEZE_BIND", "") == "imagehlp":
pytest.skip(reason="Use of lief is disabled")
pytest.importorskip("lief", reason="Depends on extra package: lief")

executable = tmp_manifest / "test_simple_manifest.exe"
assert executable.is_file()
output = run_command(tmp_manifest, executable, timeout=10)
Expand All @@ -103,6 +108,10 @@ def test_simple_manifest(tmp_manifest: Path) -> None:

def test_uac_admin(tmp_manifest: Path) -> None:
"""With the uac_admin, should return WinError 740 - requires elevation."""
if os.environ.get("CX_FREEZE_BIND", "") == "imagehlp":
pytest.skip(reason="Use of lief is disabled")
pytest.importorskip("lief", reason="Depends on extra package: lief")

executable = tmp_manifest / "test_uac_admin.exe"
assert executable.is_file()
if ctypes.windll.shell32.IsUserAnAdmin():
Expand All @@ -113,6 +122,10 @@ def test_uac_admin(tmp_manifest: Path) -> None:

def test_uac_uiaccess(tmp_manifest: Path) -> None:
"""With the uac_uiaccess, should return WinError 740."""
if os.environ.get("CX_FREEZE_BIND", "") == "imagehlp":
pytest.skip(reason="Use of lief is disabled")
pytest.importorskip("lief", reason="Depends on extra package: lief")

executable = tmp_manifest / "test_uac_uiaccess.exe"
assert executable.is_file()
if ctypes.windll.shell32.IsUserAnAdmin():
Expand Down
11 changes: 5 additions & 6 deletions tests/test_winversioninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from __future__ import annotations

import sys
from pathlib import Path
from subprocess import CalledProcessError

import pytest
from generate_samples import create_package, run_command
from packaging.version import Version

from cx_Freeze._compat import BUILD_EXE_DIR
from cx_Freeze._compat import BUILD_EXE_DIR, EXE_SUFFIX, IS_MINGW, IS_WINDOWS
from cx_Freeze.winversioninfo import COMMENTS_MAX_LEN, VersionInfo, main_test

SOURCE_SIMPLE_TEST = """
Expand All @@ -28,7 +27,7 @@
"""


@pytest.mark.skipif(sys.platform != "win32", reason="Windows tests")
@pytest.mark.skipif(not (IS_MINGW or IS_WINDOWS), reason="Windows tests")
class TestVersionInfo:
"""Test VersionInfo class."""

Expand Down Expand Up @@ -123,21 +122,21 @@ def test_windows_versions(self, input_version, version) -> None:
"""
default_version = VersionInfo(input_version)
assert default_version.version == version
assert default_version.version_info(Path("test.exe"))
assert default_version.version_info(Path(f"test{EXE_SUFFIX}"))

def test_file_not_found(self) -> None:
"""Test for FileNotFoundError exception."""
version = VersionInfo("0.1")
with pytest.raises(FileNotFoundError):
version.stamp("test.exe")
version.stamp(f"test{EXE_SUFFIX}")

@pytest.fixture
def tmp_test(self, tmp_path) -> Path:
"""Generate a executable file test.exe to be used in tests."""
create_package(tmp_path, SOURCE_SIMPLE_TEST)
run_command(tmp_path)

file_created = tmp_path / BUILD_EXE_DIR / "test.exe"
file_created = tmp_path / BUILD_EXE_DIR / f"test{EXE_SUFFIX}"
assert file_created.is_file(), f"file not found: {file_created}"

output = run_command(tmp_path, file_created, timeout=10)
Expand Down
Loading