120 lines
3.9 KiB
Python
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)
|