|
|
|
@ -18,7 +18,7 @@ import shlex |
|
|
|
|
import subprocess |
|
|
|
|
import typing as T |
|
|
|
|
|
|
|
|
|
from . import ExtensionModule, ModuleReturnValue, ModuleState, NewExtensionModule |
|
|
|
|
from . import ExtensionModule, ModuleReturnValue, NewExtensionModule |
|
|
|
|
from .. import mlog, build |
|
|
|
|
from ..compilers.compilers import CFLAGS_MAPPING, CEXE_MAPPING |
|
|
|
|
from ..dependencies import InternalDependency, PkgConfigDependency |
|
|
|
@ -27,9 +27,15 @@ from ..interpreterbase import permittedKwargs, typed_pos_args |
|
|
|
|
from ..mesonlib import (EnvironmentException, MesonException, Popen_safe, MachineChoice, |
|
|
|
|
get_variable_regex, do_replacement, extract_as_list, join_args, OptionKey) |
|
|
|
|
|
|
|
|
|
if T.TYPE_CHECKING: |
|
|
|
|
from . import ModuleState |
|
|
|
|
from ..interpreter import Interpreter |
|
|
|
|
from ..interpreterbase import TYPE_var |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExternalProject(NewExtensionModule): |
|
|
|
|
def __init__(self, |
|
|
|
|
state: ModuleState, |
|
|
|
|
state: 'ModuleState', |
|
|
|
|
configure_command: str, |
|
|
|
|
configure_options: T.List[str], |
|
|
|
|
cross_configure_options: T.List[str], |
|
|
|
@ -54,9 +60,15 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
self.src_dir = Path(self.env.get_source_dir(), self.subdir) |
|
|
|
|
self.build_dir = Path(self.env.get_build_dir(), self.subdir, 'build') |
|
|
|
|
self.install_dir = Path(self.env.get_build_dir(), self.subdir, 'dist') |
|
|
|
|
self.prefix = Path(self.env.coredata.get_option(OptionKey('prefix'))) |
|
|
|
|
self.libdir = Path(self.env.coredata.get_option(OptionKey('libdir'))) |
|
|
|
|
self.includedir = Path(self.env.coredata.get_option(OptionKey('includedir'))) |
|
|
|
|
_p = self.env.coredata.get_option(OptionKey('prefix')) |
|
|
|
|
assert isinstance(_p, str), 'for mypy' |
|
|
|
|
self.prefix = Path(_p) |
|
|
|
|
_l = self.env.coredata.get_option(OptionKey('libdir')) |
|
|
|
|
assert isinstance(_l, str), 'for mypy' |
|
|
|
|
self.libdir = Path(_l) |
|
|
|
|
_i = self.env.coredata.get_option(OptionKey('includedir')) |
|
|
|
|
assert isinstance(_i, str), 'for mypy' |
|
|
|
|
self.includedir = Path(_i) |
|
|
|
|
self.name = self.src_dir.name |
|
|
|
|
|
|
|
|
|
# On Windows if the prefix is "c:/foo" and DESTDIR is "c:/bar", `make` |
|
|
|
@ -72,7 +84,7 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
|
|
|
|
|
self.targets = self._create_targets() |
|
|
|
|
|
|
|
|
|
def _configure(self, state: ModuleState) -> None: |
|
|
|
|
def _configure(self, state: 'ModuleState') -> None: |
|
|
|
|
if self.configure_command == 'waf': |
|
|
|
|
FeatureNew('Waf external project', '0.60.0').use(self.subproject) |
|
|
|
|
waf = state.find_program('waf') |
|
|
|
@ -105,18 +117,21 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
configure_cmd += self._format_options(self.cross_configure_options, d) |
|
|
|
|
|
|
|
|
|
# Set common env variables like CFLAGS, CC, etc. |
|
|
|
|
link_exelist = [] |
|
|
|
|
link_args = [] |
|
|
|
|
link_exelist: T.List[str] = [] |
|
|
|
|
link_args: T.List[str] = [] |
|
|
|
|
self.run_env = os.environ.copy() |
|
|
|
|
for lang, compiler in self.env.coredata.compilers[MachineChoice.HOST].items(): |
|
|
|
|
if any(lang not in i for i in (CEXE_MAPPING, CFLAGS_MAPPING)): |
|
|
|
|
continue |
|
|
|
|
cargs = self.env.coredata.get_external_args(MachineChoice.HOST, lang) |
|
|
|
|
assert isinstance(cargs, list), 'for mypy' |
|
|
|
|
self.run_env[CEXE_MAPPING[lang]] = self._quote_and_join(compiler.get_exelist()) |
|
|
|
|
self.run_env[CFLAGS_MAPPING[lang]] = self._quote_and_join(cargs) |
|
|
|
|
if not link_exelist: |
|
|
|
|
link_exelist = compiler.get_linker_exelist() |
|
|
|
|
link_args = self.env.coredata.get_external_link_args(MachineChoice.HOST, lang) |
|
|
|
|
_l = self.env.coredata.get_external_link_args(MachineChoice.HOST, lang) |
|
|
|
|
assert isinstance(_l, list), 'for mypy' |
|
|
|
|
link_args = _l |
|
|
|
|
if link_exelist: |
|
|
|
|
# FIXME: Do not pass linker because Meson uses CC as linker wrapper, |
|
|
|
|
# but autotools often expects the real linker (e.h. GNU ld). |
|
|
|
@ -135,7 +150,7 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
def _quote_and_join(self, array: T.List[str]) -> str: |
|
|
|
|
return ' '.join([shlex.quote(i) for i in array]) |
|
|
|
|
|
|
|
|
|
def _validate_configure_options(self, variables: T.List[T.Tuple[str, str, str]]): |
|
|
|
|
def _validate_configure_options(self, variables: T.List[T.Tuple[str, str, str]]) -> None: |
|
|
|
|
# Ensure the user at least try to pass basic info to the build system, |
|
|
|
|
# like the prefix, libdir, etc. |
|
|
|
|
for key, default, val in variables: |
|
|
|
@ -150,10 +165,10 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
self.configure_options.append(default) |
|
|
|
|
|
|
|
|
|
def _format_options(self, options: T.List[str], variables: T.List[T.Tuple[str, str, str]]) -> T.List[str]: |
|
|
|
|
out = [] |
|
|
|
|
out: T.List[str] = [] |
|
|
|
|
missing = set() |
|
|
|
|
regex = get_variable_regex('meson') |
|
|
|
|
confdata = {k: (v, None) for k, d, v in variables} |
|
|
|
|
confdata: T.Dict[str, T.Tuple[str, T.Optional[str]]] = {k: (v, None) for k, _, v in variables} |
|
|
|
|
for o in options: |
|
|
|
|
arg, missing_vars = do_replacement(regex, o, 'meson', confdata) |
|
|
|
|
missing.update(missing_vars) |
|
|
|
@ -164,7 +179,7 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
f"Variables {var_list} in configure options are missing.") |
|
|
|
|
return out |
|
|
|
|
|
|
|
|
|
def _run(self, step: str, command: T.List[str], workdir: Path): |
|
|
|
|
def _run(self, step: str, command: T.List[str], workdir: Path) -> None: |
|
|
|
|
mlog.log(f'External project {self.name}:', mlog.bold(step)) |
|
|
|
|
m = 'Running command ' + str(command) + ' in directory ' + str(workdir) + '\n' |
|
|
|
|
log_filename = Path(mlog.log_dir, f'{self.name}-{step}.log') |
|
|
|
@ -175,16 +190,16 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
output.flush() |
|
|
|
|
else: |
|
|
|
|
mlog.log(m) |
|
|
|
|
p, o, e = Popen_safe(command, cwd=str(workdir), env=self.run_env, |
|
|
|
|
stderr=subprocess.STDOUT, |
|
|
|
|
stdout=output) |
|
|
|
|
p, *_ = Popen_safe(command, cwd=str(workdir), env=self.run_env, |
|
|
|
|
stderr=subprocess.STDOUT, |
|
|
|
|
stdout=output) |
|
|
|
|
if p.returncode != 0: |
|
|
|
|
m = f'{step} step returned error code {p.returncode}.' |
|
|
|
|
if not self.verbose: |
|
|
|
|
m += '\nSee logs: ' + str(log_filename) |
|
|
|
|
raise MesonException(m) |
|
|
|
|
|
|
|
|
|
def _create_targets(self): |
|
|
|
|
def _create_targets(self) -> T.List['TYPE_var']: |
|
|
|
|
cmd = self.env.get_build_command() |
|
|
|
|
cmd += ['--internal', 'externalproject', |
|
|
|
|
'--name', self.name, |
|
|
|
@ -220,7 +235,7 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
|
|
|
|
|
@permittedKwargs({'subdir'}) |
|
|
|
|
@typed_pos_args('external_project.dependency', str) |
|
|
|
|
def dependency_method(self, state, args: T.Tuple[str], kwargs): |
|
|
|
|
def dependency_method(self, state: 'ModuleState', args: T.Tuple[str], kwargs) -> InternalDependency: |
|
|
|
|
libname = args[0] |
|
|
|
|
|
|
|
|
|
subdir = kwargs.get('subdir', '') |
|
|
|
@ -234,29 +249,24 @@ class ExternalProject(NewExtensionModule): |
|
|
|
|
abs_libdir = Path(self.install_dir, self.rel_prefix, self.libdir) |
|
|
|
|
|
|
|
|
|
version = self.project_version |
|
|
|
|
incdir = [] |
|
|
|
|
compile_args = [f'-I{abs_includedir}'] |
|
|
|
|
link_args = [f'-L{abs_libdir}', f'-l{libname}'] |
|
|
|
|
libs = [] |
|
|
|
|
libs_whole = [] |
|
|
|
|
sources = self.target |
|
|
|
|
final_deps = [] |
|
|
|
|
variables = {} |
|
|
|
|
dep = InternalDependency(version, incdir, compile_args, link_args, libs, |
|
|
|
|
libs_whole, sources, final_deps, variables) |
|
|
|
|
dep = InternalDependency(version, [], compile_args, link_args, [], |
|
|
|
|
[], [sources], [], {}) |
|
|
|
|
return dep |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExternalProjectModule(ExtensionModule): |
|
|
|
|
@FeatureNew('External build system Module', '0.56.0') |
|
|
|
|
def __init__(self, interpreter): |
|
|
|
|
def __init__(self, interpreter: 'Interpreter'): |
|
|
|
|
super().__init__(interpreter) |
|
|
|
|
self.methods.update({'add_project': self.add_project, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
@permittedKwargs({'configure_options', 'cross_configure_options', 'verbose', 'env'}) |
|
|
|
|
@typed_pos_args('external_project_mod.add_project', str) |
|
|
|
|
def add_project(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]): |
|
|
|
|
def add_project(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue: |
|
|
|
|
configure_command = args[0] |
|
|
|
|
configure_options = extract_as_list(kwargs, 'configure_options') |
|
|
|
|
cross_configure_options = extract_as_list(kwargs, 'cross_configure_options') |
|
|
|
@ -272,5 +282,5 @@ class ExternalProjectModule(ExtensionModule): |
|
|
|
|
return ModuleReturnValue(project, project.targets) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def initialize(*args, **kwargs): |
|
|
|
|
return ExternalProjectModule(*args, **kwargs) |
|
|
|
|
def initialize(interp: 'Interpreter') -> ExternalProjectModule: |
|
|
|
|
return ExternalProjectModule(interp) |
|
|
|
|