diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-29 04:41:38 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-29 04:41:38 +0000 |
commit | 7b6e527f440cd7e6f8be2b07cee320ee6ca18786 (patch) | |
tree | 4a2738d69fa2814659fdadddf5826282e73d81f4 /mesonbuild/modules/external_project.py | |
parent | Initial commit. (diff) | |
download | meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.tar.xz meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.zip |
Adding upstream version 1.0.1.upstream/1.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mesonbuild/modules/external_project.py')
-rw-r--r-- | mesonbuild/modules/external_project.py | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/mesonbuild/modules/external_project.py b/mesonbuild/modules/external_project.py new file mode 100644 index 0000000..c3b01c8 --- /dev/null +++ b/mesonbuild/modules/external_project.py @@ -0,0 +1,307 @@ +# Copyright 2020 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +import os +import shlex +import subprocess +import typing as T + +from . import ExtensionModule, ModuleReturnValue, NewExtensionModule, ModuleInfo +from .. import mlog, build +from ..compilers.compilers import CFLAGS_MAPPING +from ..envconfig import ENV_VAR_PROG_MAP +from ..dependencies import InternalDependency, PkgConfigDependency +from ..interpreterbase import FeatureNew +from ..interpreter.type_checking import ENV_KW, DEPENDS_KW +from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args +from ..mesonlib import (EnvironmentException, MesonException, Popen_safe, MachineChoice, + get_variable_regex, do_replacement, join_args, OptionKey) + +if T.TYPE_CHECKING: + from typing_extensions import TypedDict + + from . import ModuleState + from ..interpreter import Interpreter + from ..interpreterbase import TYPE_var + from ..build import BuildTarget, CustomTarget + + class Dependency(TypedDict): + + subdir: str + + class AddProject(TypedDict): + + configure_options: T.List[str] + cross_configure_options: T.List[str] + verbose: bool + env: build.EnvironmentVariables + depends: T.List[T.Union[BuildTarget, CustomTarget]] + + +class ExternalProject(NewExtensionModule): + def __init__(self, + state: 'ModuleState', + configure_command: str, + configure_options: T.List[str], + cross_configure_options: T.List[str], + env: build.EnvironmentVariables, + verbose: bool, + extra_depends: T.List[T.Union['BuildTarget', 'CustomTarget']]): + super().__init__() + self.methods.update({'dependency': self.dependency_method, + }) + + self.subdir = Path(state.subdir) + self.project_version = state.project_version + self.subproject = state.subproject + self.env = state.environment + self.build_machine = state.build_machine + self.host_machine = state.host_machine + self.configure_command = configure_command + self.configure_options = configure_options + self.cross_configure_options = cross_configure_options + self.verbose = verbose + self.user_env = env + + 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') + _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` + # will install files into "c:/bar/c:/foo" which is an invalid path. + # Work around that issue by removing the drive from prefix. + if self.prefix.drive: + self.prefix = self.prefix.relative_to(self.prefix.drive) + + # self.prefix is an absolute path, so we cannot append it to another path. + self.rel_prefix = self.prefix.relative_to(self.prefix.root) + + self._configure(state) + + self.targets = self._create_targets(extra_depends) + + def _configure(self, state: 'ModuleState') -> None: + if self.configure_command == 'waf': + FeatureNew('Waf external project', '0.60.0').use(self.subproject, state.current_node) + waf = state.find_program('waf') + configure_cmd = waf.get_command() + configure_cmd += ['configure', '-o', str(self.build_dir)] + workdir = self.src_dir + self.make = waf.get_command() + ['build'] + else: + # Assume it's the name of a script in source dir, like 'configure', + # 'autogen.sh', etc). + configure_path = Path(self.src_dir, self.configure_command) + configure_prog = state.find_program(configure_path.as_posix()) + configure_cmd = configure_prog.get_command() + workdir = self.build_dir + self.make = state.find_program('make').get_command() + + d = [('PREFIX', '--prefix=@PREFIX@', self.prefix.as_posix()), + ('LIBDIR', '--libdir=@PREFIX@/@LIBDIR@', self.libdir.as_posix()), + ('INCLUDEDIR', None, self.includedir.as_posix()), + ] + self._validate_configure_options(d, state) + + configure_cmd += self._format_options(self.configure_options, d) + + if self.env.is_cross_build(): + host = '{}-{}-{}'.format(self.host_machine.cpu_family, + self.build_machine.system, + self.host_machine.system) + d = [('HOST', None, host)] + configure_cmd += self._format_options(self.cross_configure_options, d) + + # Set common env variables like CFLAGS, CC, etc. + 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 (ENV_VAR_PROG_MAP, CFLAGS_MAPPING)): + continue + cargs = self.env.coredata.get_external_args(MachineChoice.HOST, lang) + assert isinstance(cargs, list), 'for mypy' + self.run_env[ENV_VAR_PROG_MAP[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() + _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). + # self.run_env['LD'] = self._quote_and_join(link_exelist) + pass + self.run_env['LDFLAGS'] = self._quote_and_join(link_args) + + self.run_env = self.user_env.get_env(self.run_env) + self.run_env = PkgConfigDependency.setup_env(self.run_env, self.env, MachineChoice.HOST, + uninstalled=True) + + self.build_dir.mkdir(parents=True, exist_ok=True) + self._run('configure', configure_cmd, workdir) + + 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]], state: 'ModuleState') -> 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: + if default is None: + continue + key_format = f'@{key}@' + for option in self.configure_options: + if key_format in option: + break + else: + FeatureNew('Default configure_option', '0.57.0').use(self.subproject, state.current_node) + 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: T.List[str] = [] + missing = set() + regex = get_variable_regex('meson') + 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) + out.append(arg) + if missing: + var_list = ", ".join(repr(m) for m in sorted(missing)) + raise EnvironmentException( + f"Variables {var_list} in configure options are missing.") + return out + + 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') + output = None + if not self.verbose: + output = open(log_filename, 'w', encoding='utf-8') + output.write(m + '\n') + output.flush() + else: + mlog.log(m) + p, *_ = Popen_safe(command, cwd=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, extra_depends: T.List[T.Union['BuildTarget', 'CustomTarget']]) -> T.List['TYPE_var']: + cmd = self.env.get_build_command() + cmd += ['--internal', 'externalproject', + '--name', self.name, + '--srcdir', self.src_dir.as_posix(), + '--builddir', self.build_dir.as_posix(), + '--installdir', self.install_dir.as_posix(), + '--logdir', mlog.log_dir, + '--make', join_args(self.make), + ] + if self.verbose: + cmd.append('--verbose') + + self.target = build.CustomTarget( + self.name, + self.subdir.as_posix(), + self.subproject, + self.env, + cmd + ['@OUTPUT@', '@DEPFILE@'], + [], + [f'{self.name}.stamp'], + depfile=f'{self.name}.d', + console=True, + extra_depends=extra_depends, + ) + + idir = build.InstallDir(self.subdir.as_posix(), + Path('dist', self.rel_prefix).as_posix(), + install_dir='.', + install_dir_name='.', + install_mode=None, + exclude=None, + strip_directory=True, + from_source_dir=False, + subproject=self.subproject) + + return [self.target, idir] + + @typed_pos_args('external_project.dependency', str) + @typed_kwargs('external_project.dependency', KwargInfo('subdir', str, default='')) + def dependency_method(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'Dependency') -> InternalDependency: + libname = args[0] + + abs_includedir = Path(self.install_dir, self.rel_prefix, self.includedir) + if kwargs['subdir']: + abs_includedir = Path(abs_includedir, kwargs['subdir']) + abs_libdir = Path(self.install_dir, self.rel_prefix, self.libdir) + + version = self.project_version + compile_args = [f'-I{abs_includedir}'] + link_args = [f'-L{abs_libdir}', f'-l{libname}'] + sources = self.target + dep = InternalDependency(version, [], compile_args, link_args, [], + [], [sources], [], {}, [], []) + return dep + + +class ExternalProjectModule(ExtensionModule): + + INFO = ModuleInfo('External build system', '0.56.0', unstable=True) + + def __init__(self, interpreter: 'Interpreter'): + super().__init__(interpreter) + self.methods.update({'add_project': self.add_project, + }) + + @typed_pos_args('external_project_mod.add_project', str) + @typed_kwargs( + 'external_project.add_project', + KwargInfo('configure_options', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('cross_configure_options', ContainerTypeInfo(list, str), default=['--host=@HOST@'], listify=True), + KwargInfo('verbose', bool, default=False), + ENV_KW, + DEPENDS_KW.evolve(since='0.63.0'), + ) + def add_project(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'AddProject') -> ModuleReturnValue: + configure_command = args[0] + project = ExternalProject(state, + configure_command, + kwargs['configure_options'], + kwargs['cross_configure_options'], + kwargs['env'], + kwargs['verbose'], + kwargs['depends']) + return ModuleReturnValue(project, project.targets) + + +def initialize(interp: 'Interpreter') -> ExternalProjectModule: + return ExternalProjectModule(interp) |