mirror of
https://github.com/lief-project/LIEF
synced 2026-06-08 15:30:44 +00:00
303 lines
10 KiB
Python
303 lines
10 KiB
Python
from __future__ import annotations
|
|
from pydantic_core import ErrorDetails, core_schema, CoreSchema
|
|
from pydantic import BaseModel, ValidationError, Field, GetCoreSchemaHandler
|
|
from typing import Any, Optional, Union, List, Literal
|
|
from pathlib import Path
|
|
import tomli
|
|
import os
|
|
import platform
|
|
|
|
from typing_extensions import Annotated
|
|
|
|
from scikit_build_core.settings.skbuild_read_settings 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: Union[bool, EnvString] = Field(False, alias="stable-abi")
|
|
free_threaded: Union[bool, EnvString] = Field(False, 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")
|
|
build_dir: Optional[EnvString] = Field(None, alias="build-dir")
|
|
extra_targets: Union[List[EnvString], EnvString] = Field(None, alias="extra-targets")
|
|
extra_cmake: Union[List[EnvString], EnvString] = Field(None, alias="extra-cmake-opt")
|
|
lief_install_dir: Optional[EnvString] = Field(None, alias="lief-install-dir")
|
|
py_api: EnvString = Field("", alias="py-api")
|
|
c_compiler: Optional[EnvString] = Field(None, alias="c-compiler")
|
|
cxx_compiler: Optional[EnvString] = 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"
|
|
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}',
|
|
))
|
|
|
|
out.extend([
|
|
f"-DLIEF_PYTHON_STABLE_ABI={cmake_serialize(self.stable_abi)}",
|
|
f"-DLIEF_PYTHON_FREE_THREADED={cmake_serialize(self.free_threaded)}"
|
|
])
|
|
|
|
return out
|
|
|
|
class ThirdParty(BaseModel):
|
|
spdlog: Optional[EnvString] = None
|
|
nanobind: Optional[EnvString] = 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: Optional[EnvString] = Field(None, alias="osx-arch")
|
|
platform: Optional[EnvString] = None
|
|
pyversion: Optional[EnvString] = None
|
|
abi: Optional[EnvString] = 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: Union[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: Optional[Literal['linux', 'windows', 'android', 'osx', 'ios']] = None
|
|
architecture: Optional[Union[EnvString, Literal['arm64', 'x86_64']]] = 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(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)
|