mirror of
https://github.com/lief-project/LIEF
synced 2026-06-08 15:30:44 +00:00
326 lines
10 KiB
Python
326 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import platform
|
|
import sysconfig
|
|
from pathlib import Path
|
|
from typing import Annotated, Any, Literal
|
|
|
|
import tomli
|
|
from pydantic import BaseModel, Field, GetCoreSchemaHandler, ValidationError
|
|
from pydantic_core import CoreSchema, ErrorDetails, core_schema
|
|
from scikit_build_core._logging import rich_print
|
|
|
|
|
|
def cmake_serialize(field: Any):
|
|
if isinstance(field, bool):
|
|
return "ON" if field else "OFF"
|
|
return field
|
|
|
|
|
|
class EnvStringValidator:
|
|
def _get_env_string(self, string: str) -> str:
|
|
winpy_architecture = os.getenv("LIEF_TARGET_ARCHITECTURE", "")
|
|
if winpy_architecture == "x86_64":
|
|
winpy_architecture = "amd64"
|
|
formatted = string.format(
|
|
python_version=os.getenv("LIEF_TARGET_PYTHON_VERSION", ""),
|
|
python_version_alt=os.getenv("LIEF_TARGET_PYTHON_VERSION", "").replace(
|
|
".", ""
|
|
),
|
|
architecture=os.getenv("LIEF_TARGET_ARCHITECTURE", ""),
|
|
winpy_architecture=winpy_architecture,
|
|
ci_project_dir=os.getenv("CI_PROJECT_DIR", ""),
|
|
stable_abi=os.getenv("LIEF_STABLE_ABI", ""),
|
|
free_threaded=os.getenv("LIEF_FREE_THREADED", ""),
|
|
runtime=os.getenv("LIEF_RUNTIME", "false"),
|
|
)
|
|
return formatted
|
|
|
|
def __get_pydantic_core_schema__(
|
|
self, source_type: Any, handler: GetCoreSchemaHandler
|
|
) -> CoreSchema:
|
|
return core_schema.no_info_after_validator_function(
|
|
self._get_env_string, handler(source_type)
|
|
)
|
|
|
|
|
|
EnvString = Annotated[str, EnvStringValidator()]
|
|
|
|
|
|
class BuildConfig(BaseModel):
|
|
build_type: EnvString = Field("Release", alias="type")
|
|
cache: bool = True
|
|
ninja: bool = False
|
|
stable_abi: bool | EnvString = Field(False, alias="stable-abi")
|
|
free_threaded: bool | EnvString | None = Field(None, alias="free-threaded")
|
|
default_target: str = Field("pyLIEF", alias="default-target")
|
|
parallel_jobs: int = Field(0, alias="parallel-jobs")
|
|
compilation_flags: list[str] = Field([], alias="compilation-flags")
|
|
lief_extra_compilation_flags: list[str] = Field(
|
|
[], alias="lief-extra-compilation-flags"
|
|
)
|
|
build_dir: EnvString | None = Field(None, alias="build-dir")
|
|
extra_targets: list[EnvString] | EnvString = Field([], alias="extra-targets")
|
|
extra_cmake: list[EnvString] | EnvString = Field([], alias="extra-cmake-opt")
|
|
lief_install_dir: EnvString | None = Field(None, alias="lief-install-dir")
|
|
py_api: EnvString = Field("", alias="py-api")
|
|
c_compiler: EnvString | None = Field(None, alias="c-compiler")
|
|
cxx_compiler: EnvString | None = Field(None, alias="cxx-compiler")
|
|
|
|
@property
|
|
def targets(self) -> list[str]:
|
|
default_targets: list[str] = [self.default_target]
|
|
if self.extra_targets is not None:
|
|
if isinstance(self.extra_targets, str):
|
|
default_targets.append(self.extra_targets)
|
|
else:
|
|
default_targets.extend(self.extra_targets)
|
|
|
|
return default_targets
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
out: list[str] = [f"-DLIEF_USE_CCACHE={cmake_serialize(self.cache)}"]
|
|
|
|
if self.extra_cmake is not None:
|
|
if isinstance(self.extra_cmake, str):
|
|
out.append(self.extra_cmake)
|
|
elif isinstance(self.extra_cmake, list):
|
|
out.extend(self.extra_cmake)
|
|
|
|
if self.lief_install_dir is not None:
|
|
lief_cmake_dir = Path(self.lief_install_dir) / "lib" / "cmake" / "LIEF"
|
|
if not lief_cmake_dir.is_dir():
|
|
raise RuntimeError(f"Invalid install dir: {lief_cmake_dir}")
|
|
lief_dir = lief_cmake_dir.expanduser().resolve().absolute()
|
|
out.extend(
|
|
(
|
|
"-DLIEF_PY_LIEF_EXT=on",
|
|
"-DLIEF_INSTALL=off",
|
|
f"-DLIEF_DIR={lief_dir.as_posix()}",
|
|
)
|
|
)
|
|
|
|
if self.c_compiler is not None:
|
|
out.append(f"-DCMAKE_C_COMPILER={self.c_compiler}")
|
|
|
|
if self.cxx_compiler is not None:
|
|
out.append(f"-DCMAKE_CXX_COMPILER={self.cxx_compiler}")
|
|
|
|
if len(self.compilation_flags) > 0:
|
|
flags = " ".join(self.compilation_flags)
|
|
out.extend(
|
|
(
|
|
f"-DCMAKE_CXX_FLAGS={flags}",
|
|
f"-DCMAKE_C_FLAGS={flags}",
|
|
)
|
|
)
|
|
|
|
if len(self.lief_extra_compilation_flags) > 0:
|
|
flags = ";".join(self.lief_extra_compilation_flags)
|
|
out.extend((f"-DLIEF_EXTRA_FLAGS={flags}",))
|
|
|
|
free_threaded = self.free_threaded
|
|
if free_threaded is None:
|
|
free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
|
|
|
|
out.extend(
|
|
[
|
|
f"-DLIEF_PYTHON_STABLE_ABI={cmake_serialize(self.stable_abi)}",
|
|
f"-DLIEF_PYTHON_FREE_THREADED={cmake_serialize(free_threaded)}",
|
|
]
|
|
)
|
|
|
|
return out
|
|
|
|
|
|
class ThirdParty(BaseModel):
|
|
spdlog: EnvString | None = None
|
|
nanobind: EnvString | None = None
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
out: list[str] = []
|
|
if self.spdlog is not None:
|
|
out.extend(("-DLIEF_EXTERNAL_SPDLOG=ON", f"-Dspdlog_DIR={self.spdlog}"))
|
|
|
|
if self.nanobind is not None:
|
|
out.extend(
|
|
("-DLIEF_OPT_NANOBIND_EXTERNAL=ON", f"-Dnanobind_DIR={self.nanobind}")
|
|
)
|
|
|
|
return out
|
|
|
|
|
|
class CrossCompilation(BaseModel):
|
|
osx_arch: EnvString | None = Field(None, alias="osx-arch")
|
|
platform: EnvString | None = None
|
|
pyversion: EnvString | None = None
|
|
abi: EnvString | None = None
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
out: list[str] = []
|
|
if self.osx_arch is not None:
|
|
out.extend((f"-DCMAKE_OSX_ARCHITECTURES={self.osx_arch}",))
|
|
return out
|
|
|
|
|
|
class Formats(BaseModel):
|
|
elf: bool = True
|
|
pe: bool = True
|
|
macho: bool = True
|
|
coff: bool = True
|
|
dex: bool = True
|
|
art: bool = True
|
|
oat: bool = True
|
|
vdex: bool = True
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
return [
|
|
f"-DLIEF_ELF={cmake_serialize(self.elf)}",
|
|
f"-DLIEF_PE={cmake_serialize(self.pe)}",
|
|
f"-DLIEF_MACHO={cmake_serialize(self.macho)}",
|
|
f"-DLIEF_COFF={cmake_serialize(self.coff)}",
|
|
f"-DLIEF_DEX={cmake_serialize(self.dex)}",
|
|
f"-DLIEF_OAT={cmake_serialize(self.oat)}",
|
|
f"-DLIEF_ART={cmake_serialize(self.art)}",
|
|
f"-DLIEF_VDEX={cmake_serialize(self.vdex)}",
|
|
]
|
|
|
|
|
|
class Logging(BaseModel):
|
|
enabled: bool = True
|
|
debug: bool = False
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
return [
|
|
f"-DLIEF_LOGGING={cmake_serialize(self.enabled)}",
|
|
f"-DLIEF_LOGGING_DEBUG={cmake_serialize(self.debug)}",
|
|
]
|
|
|
|
|
|
class Features(BaseModel):
|
|
# json is an attribute already defined in BaseModel
|
|
json_support: bool = Field(True, alias="json")
|
|
frozen: bool = True
|
|
runtime: bool | EnvString = Field(False)
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
return [
|
|
f"-DLIEF_RUNTIME={cmake_serialize(self.runtime)}",
|
|
f"-DLIEF_ENABLE_JSON={cmake_serialize(self.json_support)}",
|
|
f"-DLIEF_DISABLE_FROZEN={cmake_serialize(not self.frozen)}",
|
|
]
|
|
|
|
|
|
class Runtime(BaseModel):
|
|
platform: Literal["linux", "windows", "android", "osx", "ios"] | None = None
|
|
architecture: EnvString | Literal["arm64", "x86_64"] | None = None
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
out: list[str] = []
|
|
|
|
if self.platform is not None:
|
|
out.append(f"-DLIEF_RUNTIME_PLATFORM={self.platform}")
|
|
|
|
if self.architecture is not None:
|
|
out.append(f"-DLIEF_RUNTIME_ARCH={self.architecture}")
|
|
|
|
return out
|
|
|
|
|
|
class ConfigT(BaseModel):
|
|
build: BuildConfig = BuildConfig()
|
|
formats: Formats = Formats()
|
|
third_party: ThirdParty = Field(ThirdParty(), alias="third-party")
|
|
features: Features = Features()
|
|
logging: Logging = Logging()
|
|
runtime: Runtime = Runtime()
|
|
cross_compilation: CrossCompilation = Field(
|
|
CrossCompilation(), alias="cross-compilation"
|
|
)
|
|
|
|
def _cmake_base_args(self) -> list[str]:
|
|
return [
|
|
"-DLIEF_PYTHON_API=on",
|
|
"-DLIEF_INSTALL=off",
|
|
"-DLIEF_INSTALL_COMPILED_EXAMPLES=off",
|
|
]
|
|
|
|
def cmake_dump(self) -> list[str]:
|
|
return [
|
|
*self._cmake_base_args(),
|
|
*self.build.cmake_dump(),
|
|
*self.formats.cmake_dump(),
|
|
*self.third_party.cmake_dump(),
|
|
*self.features.cmake_dump(),
|
|
*self.logging.cmake_dump(),
|
|
*self.runtime.cmake_dump(),
|
|
*self.cross_compilation.cmake_dump(),
|
|
]
|
|
|
|
|
|
def pretty_error(err: ErrorDetails, file: Path):
|
|
loc = ".".join([str(e) for e in err["loc"]])
|
|
if err["type"] == "value_error.missing":
|
|
rich_print(f"[red]'{loc}' is missing in {file}")
|
|
else:
|
|
rich_print(f"[red]Error with '{loc}' in {file} ({err['msg']})")
|
|
|
|
|
|
class Config:
|
|
DEFAULT_EXCLUDE: list[str] = ["*.so", "*.pyd"]
|
|
|
|
def __init__(self, config: ConfigT):
|
|
self._config: ConfigT = config
|
|
|
|
@classmethod
|
|
def from_file(cls, file: Path):
|
|
with open(file, "rb") as f:
|
|
toml_config = tomli.load(f)
|
|
|
|
try:
|
|
return cls(ConfigT.model_validate(toml_config["lief"]))
|
|
except ValidationError as e:
|
|
for err in e.errors():
|
|
pretty_error(err, file)
|
|
return None
|
|
|
|
@property
|
|
def strip(self) -> str:
|
|
return str(self._config.build.build_type.lower() == "release")
|
|
|
|
@property
|
|
def build_dir(self) -> str:
|
|
if bdir := os.getenv("LIEF_BUILD_DIR", None):
|
|
return bdir
|
|
|
|
if bdir := self._config.build.build_dir:
|
|
return bdir
|
|
|
|
return ""
|
|
|
|
def get_cmake_args(self, editable: bool = False) -> list[str]:
|
|
cmake_args = self._config.cmake_dump()
|
|
if editable:
|
|
cmake_args.append(
|
|
"-DLIEF_PYTHON_EDITABLE=ON",
|
|
)
|
|
return cmake_args
|
|
|
|
@property
|
|
def cmake_generator(self) -> list[str]:
|
|
if self._config.build.ninja:
|
|
return [
|
|
"-GNinja",
|
|
]
|
|
return []
|
|
|
|
@property
|
|
def osx_arch(self) -> str:
|
|
return self.cross_compilation.osx_arch or platform.machine()
|
|
|
|
def __getattr__(self, name):
|
|
if value := getattr(self._config, name, None):
|
|
return value
|
|
return self.__getattribute__(name)
|