mirror of
https://github.com/QuasarApp/LIEF.git
synced 2025-04-26 04:14:33 +00:00
461 lines
16 KiB
Python
461 lines
16 KiB
Python
import os
|
|
import sys
|
|
import platform
|
|
import subprocess
|
|
import setuptools
|
|
import pathlib
|
|
import sysconfig
|
|
import copy
|
|
import distutils
|
|
from pkg_resources import Distribution, get_distribution
|
|
from setuptools import setup, Extension
|
|
from setuptools.command.build_ext import build_ext, copy_file
|
|
from distutils import log
|
|
|
|
from distutils.version import LooseVersion
|
|
|
|
|
|
MIN_SETUPTOOLS_VERSION = "31.0.0"
|
|
assert (LooseVersion(setuptools.__version__) >= LooseVersion(MIN_SETUPTOOLS_VERSION)), "LIEF requires a setuptools version '{}' or higher (pip install setuptools --upgrade)".format(MIN_SETUPTOOLS_VERSION)
|
|
|
|
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
|
|
PACKAGE_NAME = "lief"
|
|
|
|
get_config_var_backup = sysconfig.get_config_var
|
|
get_platform_backup = sysconfig.get_platform
|
|
get_config_vars_backup = sysconfig.get_config_vars
|
|
distutils_get_config_vars_backup = distutils.sysconfig.get_config_vars
|
|
|
|
class LiefDistribution(setuptools.Distribution):
|
|
global_options = setuptools.Distribution.global_options + [
|
|
('lief-test', None, 'Build and make tests'),
|
|
('ninja', None, 'Use Ninja as build system'),
|
|
('sdk', None, 'Build SDK package'),
|
|
('doc', None, 'Build LIEF documentation'),
|
|
|
|
('lief-no-json', None, 'Disable JSON module'),
|
|
('lief-no-logging', None, 'Disable logging module'),
|
|
|
|
('lief-no-elf', None, 'Disable ELF module'),
|
|
('lief-no-pe', None, 'Disable PE module'),
|
|
('lief-no-macho', None, 'Disable Mach-O module'),
|
|
|
|
('lief-no-android', None, 'Disable Android formats'),
|
|
('lief-no-art', None, 'Disable ART module'),
|
|
('lief-no-vdex', None, 'Disable VDEX module'),
|
|
('lief-no-oat', None, 'Disable OAT module'),
|
|
('lief-no-dex', None, 'Disable DEX module'),
|
|
]
|
|
|
|
def __init__(self, attrs=None):
|
|
self.lief_test = False
|
|
self.ninja = False
|
|
self.sdk = False
|
|
|
|
self.lief_no_json = False
|
|
self.lief_no_logging = False
|
|
|
|
self.lief_no_elf = False
|
|
self.lief_no_pe = False
|
|
self.lief_no_macho = False
|
|
|
|
self.lief_no_art = False
|
|
self.lief_no_oat = False
|
|
self.lief_no_dex = False
|
|
self.lief_no_vdex = False
|
|
|
|
self.lief_no_android = False
|
|
self.doc = False
|
|
super().__init__(attrs)
|
|
|
|
|
|
class Module(Extension):
|
|
def __init__(self, name, sourcedir='', *args, **kwargs):
|
|
Extension.__init__(self, name, sources=[])
|
|
self.sourcedir = os.path.abspath(os.path.join(CURRENT_DIR))
|
|
|
|
|
|
class BuildLibrary(build_ext):
|
|
def run(self):
|
|
try:
|
|
subprocess.check_output(['cmake', '--version'])
|
|
except OSError:
|
|
raise RuntimeError("CMake must be installed to build the following extensions: " +
|
|
", ".join(e.name for e in self.extensions))
|
|
|
|
for ext in self.extensions:
|
|
self.build_extension(ext)
|
|
self.copy_extensions_to_source()
|
|
|
|
@staticmethod
|
|
def has_ninja():
|
|
try:
|
|
subprocess.check_call(['ninja', '--version'])
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
@staticmethod
|
|
def sdk_suffix():
|
|
if platform.system() == "Windows":
|
|
return "zip"
|
|
return "tar.gz"
|
|
|
|
def build_extension(self, ext):
|
|
if self.distribution.lief_test:
|
|
log.info("LIEF tests enabled!")
|
|
fullname = self.get_ext_fullname(ext.name)
|
|
filename = self.get_ext_filename(fullname)
|
|
|
|
jobs = self.parallel if self.parallel else 1
|
|
cmake_args = []
|
|
|
|
source_dir = ext.sourcedir
|
|
build_temp = self.build_temp
|
|
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
|
|
cmake_library_output_directory = os.path.abspath(os.path.dirname(build_temp))
|
|
cfg = 'RelWithDebInfo' if self.debug else 'Release'
|
|
is64 = sys.maxsize > 2**32
|
|
|
|
# Ninja ?
|
|
build_with_ninja = False
|
|
if self.has_ninja() and self.distribution.ninja:
|
|
build_with_ninja = True
|
|
|
|
if build_with_ninja:
|
|
cmake_args += ["-G", "Ninja"]
|
|
|
|
cmake_args += [
|
|
'-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}'.format(cmake_library_output_directory),
|
|
'-DPYTHON_EXECUTABLE={}'.format(sys.executable),
|
|
'-DLIEF_PYTHON_API=on',
|
|
]
|
|
|
|
# LIEF options
|
|
# ============
|
|
if self.distribution.lief_test:
|
|
cmake_args += ["-DLIEF_TESTS=on"]
|
|
|
|
if self.distribution.lief_no_json:
|
|
log.info("LIEF JSON module disabled")
|
|
cmake_args += ["-DLIEF_ENABLE_JSON=off"]
|
|
|
|
if self.distribution.lief_no_logging:
|
|
log.info("LIEF logging module disabled")
|
|
cmake_args += ["-DLIEF_LOGGING=off"]
|
|
|
|
if self.distribution.doc:
|
|
log.info("LIEF documentation enabled")
|
|
cmake_args += ["-DLIEF_DOC=on"]
|
|
|
|
if self.debug:
|
|
log.info("LIEF enables DEBUG messages")
|
|
cmake_args += ["-DLIEF_LOGGING_DEBUG=on"]
|
|
else:
|
|
cmake_args += ["-DLIEF_LOGGING_DEBUG=off"]
|
|
|
|
# Main formats
|
|
# ============
|
|
if self.distribution.lief_no_elf:
|
|
log.info("LIEF ELF module disabled")
|
|
cmake_args += ["-DLIEF_ELF=off"]
|
|
|
|
if self.distribution.lief_no_pe:
|
|
log.info("LIEF PE module disabled")
|
|
cmake_args += ["-DLIEF_PE=off"]
|
|
|
|
if self.distribution.lief_no_macho:
|
|
log.info("LIEF MACH-O module disabled")
|
|
cmake_args += ["-DLIEF_MACHO=off"]
|
|
|
|
# Android formats
|
|
# ===============
|
|
if self.distribution.lief_no_oat or self.distribution.lief_no_android:
|
|
log.info("LIEF OAT module disabled")
|
|
cmake_args += ["-DLIEF_OAT=off"]
|
|
|
|
if self.distribution.lief_no_dex or self.distribution.lief_no_android:
|
|
log.info("LIEF DEX module disabled")
|
|
cmake_args += ["-DLIEF_DEX=off"]
|
|
|
|
if self.distribution.lief_no_vdex or self.distribution.lief_no_android:
|
|
log.info("LIEF VDEX module disabled")
|
|
cmake_args += ["-DLIEF_VDEX=off"]
|
|
|
|
if self.distribution.lief_no_art or self.distribution.lief_no_android:
|
|
log.info("LIEF ART module disabled")
|
|
cmake_args += ["-DLIEF_ART=off"]
|
|
|
|
build_args = ['--config', cfg]
|
|
|
|
env = os.environ
|
|
|
|
if os.getenv("CXXFLAGS", None) is not None:
|
|
cmake_args += [
|
|
'-DCMAKE_CXX_FLAGS={}'.format(os.getenv("CXXFLAGS")),
|
|
]
|
|
|
|
if os.getenv("CFLAGS", None) is not None:
|
|
cmake_args += [
|
|
'-DCMAKE_C_FLAGS={}'.format(os.getenv("CFLAGS")),
|
|
]
|
|
|
|
|
|
if platform.system() == "Windows":
|
|
from setuptools import msvc
|
|
|
|
cmake_args += [
|
|
'-DCMAKE_BUILD_TYPE={}'.format(cfg),
|
|
'-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), cmake_library_output_directory),
|
|
'-DLIEF_USE_CRT_RELEASE=MT',
|
|
]
|
|
if build_with_ninja:
|
|
arch = 'x64' if is64 else 'x86'
|
|
ninja_env = msvc.msvc14_get_vc_env(arch)
|
|
env.update(ninja_env)
|
|
else:
|
|
cmake_args += ['-A', 'x64'] if is64 else ['-A', 'win32']
|
|
build_args += ['--', '/m']
|
|
else:
|
|
cmake_args += ['-DCMAKE_BUILD_TYPE={}'.format(cfg)]
|
|
|
|
|
|
if not os.path.exists(self.build_temp):
|
|
os.makedirs(self.build_temp)
|
|
|
|
log.info("Platform: %s", platform.system())
|
|
log.info("Wheel library: %s", self.get_ext_fullname(ext.name))
|
|
|
|
# 1. Configure
|
|
configure_cmd = ['cmake', ext.sourcedir] + cmake_args
|
|
log.info(" ".join(configure_cmd))
|
|
subprocess.check_call(configure_cmd, cwd=self.build_temp, env=env)
|
|
|
|
# 2. Build
|
|
targets = {
|
|
'python_bindings': 'pyLIEF',
|
|
}
|
|
if self.distribution.sdk:
|
|
targets['sdk'] = "package"
|
|
|
|
if self.distribution.doc:
|
|
targets['doc'] = "lief-doc"
|
|
|
|
if platform.system() == "Windows":
|
|
build_cmd = ['cmake', '--build', '.', '--target', "lief_samples"] + build_args
|
|
#log.info(" ".join(build_cmd))
|
|
|
|
if self.distribution.lief_test:
|
|
subprocess.check_call(['cmake', '--build', '.', '--target', "lief_samples"] + build_args, cwd=self.build_temp, env=env)
|
|
subprocess.check_call(configure_cmd, cwd=self.build_temp, env=env)
|
|
if build_with_ninja:
|
|
subprocess.check_call(['cmake', '--build', '.', '--target', "all"] + build_args, cwd=self.build_temp, env=env)
|
|
else:
|
|
subprocess.check_call(['cmake', '--build', '.', '--target', "ALL_BUILD"] + build_args, cwd=self.build_temp, env=env)
|
|
subprocess.check_call(['cmake', '--build', '.', '--target', "check-lief"] + build_args, cwd=self.build_temp, env=env)
|
|
else:
|
|
subprocess.check_call(['cmake', '--build', '.', '--target', targets['python_bindings']] + build_args, cwd=self.build_temp, env=env)
|
|
|
|
if 'sdk' in targets:
|
|
subprocess.check_call(['cmake', '--build', '.', '--target', targets['sdk']] + build_args, cwd=self.build_temp, env=env)
|
|
|
|
else:
|
|
if build_with_ninja:
|
|
if self.distribution.lief_test:
|
|
subprocess.check_call(['ninja', "lief_samples"], cwd=self.build_temp)
|
|
subprocess.check_call(configure_cmd, cwd=self.build_temp)
|
|
subprocess.check_call(['ninja'], cwd=self.build_temp)
|
|
subprocess.check_call(['ninja', "check-lief"], cwd=self.build_temp)
|
|
else:
|
|
subprocess.check_call(['ninja', targets['python_bindings']], cwd=self.build_temp, env=env)
|
|
|
|
if 'sdk' in targets:
|
|
subprocess.check_call(['ninja', targets['sdk']], cwd=self.build_temp, env=env)
|
|
|
|
if 'doc' in targets:
|
|
try:
|
|
subprocess.check_call(['ninja', targets['doc']], cwd=self.build_temp, env=env)
|
|
except Exception as e:
|
|
log.error("Documentation failed: %s" % e)
|
|
else:
|
|
log.info("Using {} jobs".format(jobs))
|
|
if self.distribution.lief_test:
|
|
subprocess.check_call(['make', '-j', str(jobs), "lief_samples"], cwd=self.build_temp)
|
|
subprocess.check_call(configure_cmd, cwd=self.build_temp)
|
|
subprocess.check_call(['make', '-j', str(jobs), "all"], cwd=self.build_temp)
|
|
subprocess.check_call(['make', '-j', str(jobs), "check-lief"], cwd=self.build_temp)
|
|
else:
|
|
subprocess.check_call(['make', '-j', str(jobs), targets['python_bindings']], cwd=self.build_temp, env=env)
|
|
|
|
if 'sdk' in targets:
|
|
subprocess.check_call(['make', '-j', str(jobs), targets['sdk']], cwd=self.build_temp, env=env)
|
|
|
|
if 'doc' in targets:
|
|
try:
|
|
subprocess.check_call(['make', '-j', str(jobs), targets['doc']], cwd=self.build_temp, env=env)
|
|
except Exception as e:
|
|
log.error("Documentation failed: %s" % e)
|
|
pylief_dst = os.path.join(self.build_lib, self.get_ext_filename(self.get_ext_fullname(ext.name)))
|
|
libsuffix = pylief_dst.split(".")[-1]
|
|
|
|
pylief_path = os.path.join(cmake_library_output_directory, "{}.{}".format(PACKAGE_NAME, libsuffix))
|
|
if platform.system() == "Windows":
|
|
pylief_base = pathlib.Path(cmake_library_output_directory) / "Release" / "api" / "python"
|
|
pylief_path = pylief_base / "Release" / "{}.{}".format(PACKAGE_NAME, libsuffix)
|
|
if not pylief_path.is_file():
|
|
pylief_path = pylief_base / "{}.{}".format(PACKAGE_NAME, libsuffix)
|
|
|
|
pylief_path = pylief_path.as_posix()
|
|
|
|
if not os.path.exists(self.build_lib):
|
|
os.makedirs(self.build_lib)
|
|
|
|
log.info("Copying {} into {}".format(pylief_path, pylief_dst))
|
|
copy_file(
|
|
pylief_path, pylief_dst, verbose=self.verbose,
|
|
dry_run=self.dry_run)
|
|
|
|
|
|
# SDK
|
|
# ===
|
|
if self.distribution.sdk:
|
|
sdk_path = list(pathlib.Path(self.build_temp).rglob("LIEF-*.{}".format(self.sdk_suffix())))
|
|
if len(sdk_path) == 0:
|
|
log.error("Unable to find SDK archive")
|
|
sys.exit(1)
|
|
|
|
sdk_path = str(sdk_path.pop())
|
|
sdk_output = str(pathlib.Path(CURRENT_DIR) / "build")
|
|
|
|
copy_file(
|
|
sdk_path, sdk_output, verbose=self.verbose,
|
|
dry_run=self.dry_run)
|
|
|
|
def get_platform():
|
|
out = get_platform_backup()
|
|
lief_arch = os.environ.get("LIEF_PY_XARCH", None)
|
|
if lief_arch is not None and isinstance(out, str):
|
|
original_out = out
|
|
out = out.replace("x86_64", lief_arch)
|
|
log.info(" Replace %s -> %s", original_out, out)
|
|
return out
|
|
|
|
def get_config_vars(*args):
|
|
out = get_config_vars_backup(*args)
|
|
lief_arch = os.environ.get("LIEF_PY_XARCH", None)
|
|
if lief_arch is None:
|
|
return out
|
|
out_xfix = copy.deepcopy(out)
|
|
for k, v in out.items():
|
|
if not (isinstance(v, str) and "x86_64" in v):
|
|
continue
|
|
if k not in {"SO", "SOABI", "EXT_SUFFIX", "BUILD_GNU_TYPE"}:
|
|
continue
|
|
fix = v.replace("x86_64", lief_arch)
|
|
log.info(" Replace %s: %s -> %s", k, v, fix)
|
|
out_xfix[k] = fix
|
|
|
|
return out_xfix
|
|
|
|
|
|
def distutils_get_config_vars(*args):
|
|
out = distutils_get_config_vars_backup(*args)
|
|
lief_arch = os.environ.get("LIEF_PY_XARCH", None)
|
|
if lief_arch is None:
|
|
return out
|
|
|
|
if isinstance(out, list):
|
|
fixes = []
|
|
for item in out:
|
|
if not (isinstance(item, str) and "x86_64" in item):
|
|
fixes.append(item)
|
|
else:
|
|
fixes.append(item.replace("x86_64", lief_arch))
|
|
return fixes
|
|
|
|
out_xfix = copy.deepcopy(out)
|
|
for k, v in out.items():
|
|
if not (isinstance(v, str) and "x86_64" in v):
|
|
continue
|
|
if k not in {"SO", "SOABI", "EXT_SUFFIX", "BUILD_GNU_TYPE"}:
|
|
continue
|
|
fix = v.replace("x86_64", lief_arch)
|
|
log.info(" Replace %s: %s -> %s", k, v, fix)
|
|
out_xfix[k] = fix
|
|
|
|
return out_xfix
|
|
|
|
sysconfig.get_platform = get_platform
|
|
sysconfig.get_config_vars = get_config_vars
|
|
distutils.sysconfig.get_config_vars = distutils_get_config_vars
|
|
|
|
# From setuptools-git-version
|
|
command = 'git describe --tags --long --dirty'
|
|
is_tagged_cmd = 'git tag --list --points-at=HEAD'
|
|
fmt_dev = '{tag}.dev0'
|
|
fmt_tagged = '{tag}'
|
|
|
|
def format_version(version: str, fmt: str = fmt_dev, is_dev: bool = False):
|
|
parts = version.split('-')
|
|
assert len(parts) in (3, 4)
|
|
dirty = len(parts) == 4
|
|
tag, count, sha = parts[:3]
|
|
MA, MI, PA = map(int, tag.split(".")) # 0.9.0 -> (0, 9, 0)
|
|
|
|
if is_dev:
|
|
tag = "{}.{}.{}".format(MA, MI + 1, 0)
|
|
|
|
if count == '0' and not dirty:
|
|
return tag
|
|
return fmt.format(tag=tag, gitsha=sha.lstrip('g'))
|
|
|
|
|
|
def get_git_version(is_tagged: bool) -> str:
|
|
git_version = subprocess.check_output(command.split()).decode('utf-8').strip()
|
|
if is_tagged:
|
|
return format_version(version=git_version, fmt=fmt_tagged)
|
|
return format_version(version=git_version, fmt=fmt_dev, is_dev=True)
|
|
|
|
def check_if_tagged() -> bool:
|
|
output = subprocess.check_output(is_tagged_cmd.split()).decode('utf-8').strip()
|
|
return output != ""
|
|
|
|
def get_pkg_info_version(pkg_info_file):
|
|
pkg = get_distribution(PACKAGE_NAME)
|
|
return pkg.version
|
|
|
|
|
|
def get_version() -> str:
|
|
version = "0.11.2"
|
|
pkg_info = os.path.join(CURRENT_DIR, "{}.egg-info".format(PACKAGE_NAME), "PKG-INFO")
|
|
git_dir = os.path.join(CURRENT_DIR, ".git")
|
|
if os.path.isdir(git_dir):
|
|
is_tagged = False
|
|
try:
|
|
is_tagged = check_if_tagged()
|
|
except Exception:
|
|
is_tagged = False
|
|
|
|
try:
|
|
return get_git_version(is_tagged)
|
|
except Exception:
|
|
pass
|
|
|
|
if os.path.isfile(pkg_info):
|
|
return get_pkg_info_version(pkg_info)
|
|
|
|
return version
|
|
|
|
version = get_version()
|
|
print(version)
|
|
cmdclass = {
|
|
'build_ext': BuildLibrary,
|
|
}
|
|
|
|
setup(
|
|
distclass=LiefDistribution,
|
|
ext_modules=[Module(PACKAGE_NAME)],
|
|
cmdclass=cmdclass,
|
|
version=version
|
|
)
|