Files

120 lines
3.9 KiB
Python

"""Common utility functions shared between cx_Freeze modules."""
from __future__ import annotations
from contextlib import suppress
from pathlib import Path, PurePath
from textwrap import dedent
from types import CodeType
from typing import TYPE_CHECKING
from cx_Freeze.exception import OptionError
if TYPE_CHECKING:
from cx_Freeze._typing import IncludesList, InternalIncludesList
def get_resource_file_path(
dirname: str | Path, name: str | Path, ext: str
) -> Path | None:
"""Return the path to a resource file shipped with cx_Freeze.
This is used to find our base executables and initscripts when they are
just specified by name.
"""
pname = Path(name)
if pname.is_absolute():
return pname
pname = Path(__file__).resolve().parent / dirname / pname.with_suffix(ext)
if pname.exists():
return pname
# Support for name argument in the old Camelcase value
pname = pname.with_name(pname.name.lower())
if pname.exists():
return pname
return None
def normalize_to_list(
value: str | list[str] | tuple[str, ...] | None,
) -> list[str]:
"""Takes the different formats of options containing multiple values and
returns the value as a list object.
"""
if not value: # empty or None
return []
if isinstance(value, str):
return value.split(",")
return list(value)
def process_path_specs(specs: IncludesList | None) -> InternalIncludesList:
"""Prepare paths specified as config.
The input is a list of either strings, or 2-tuples (source, target).
Where single strings are supplied, the basenames are used as targets.
Where targets are given explicitly, they must not be absolute paths.
Returns a list of 2-tuples, or throws OptionError if something is wrong
in the input.
"""
if specs is None:
specs = []
processed_specs: InternalIncludesList = []
for spec in specs:
if not isinstance(spec, (list, tuple)):
source = spec
target = None
elif len(spec) != 2:
msg = "path spec must be a list or tuple of length two"
raise OptionError(msg)
else:
source, target = spec
source = Path(source)
if not source.exists():
msg = f"cannot find file/directory named {source!s}"
raise OptionError(msg)
target = PurePath(target or source.name)
if target.is_absolute():
msg = f"target path named {target!s} cannot be absolute"
raise OptionError(msg)
processed_specs.append((source, target))
return processed_specs
def code_object_replace(code: CodeType, **kwargs) -> CodeType:
"""Return a copy of the code object with new values for the specified
fields.
"""
with suppress(ValueError, KeyError):
kwargs["co_consts"] = tuple(kwargs["co_consts"])
return code.replace(**kwargs)
def code_object_replace_function(
code: CodeType, name: str, source: str
) -> CodeType:
"""Return a copy of the code object with the function 'name' replaced."""
if code is None:
return code
new_code = compile(
dedent(source), code.co_filename, "exec", dont_inherit=True
)
new_co_func = None
for constant in new_code.co_consts:
if isinstance(constant, CodeType) and constant.co_name == name:
new_co_func = constant
break
if new_co_func is None:
return code
consts = list(code.co_consts)
for i, constant in enumerate(consts):
if isinstance(constant, CodeType) and constant.co_name == name:
consts[i] = code_object_replace(
new_co_func, co_firstlineno=constant.co_firstlineno
)
break
return code_object_replace(code, co_consts=consts)