diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2018-10-04 00:07:44 -0400 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2020-09-13 13:54:47 -0400 |
commit | 9d338200dacdf24c50259c309380200f8a53d5b5 (patch) | |
tree | 8e268a9357119265f11b30791f56e8e09fec393e | |
parent | 19696c3dcdb379f4c5b88457f43242f2d33427a0 (diff) | |
download | meson-9d338200dacdf24c50259c309380200f8a53d5b5.zip meson-9d338200dacdf24c50259c309380200f8a53d5b5.tar.gz meson-9d338200dacdf24c50259c309380200f8a53d5b5.tar.bz2 |
external-project: New module to build configure/make projects
This adds an experimental meson module to build projects with other
build systems.
Closes: #4316
20 files changed, 654 insertions, 19 deletions
@@ -1,5 +1,6 @@ * @jpakkane /mesonbuild/modules/pkgconfig.py @xclaesse /mesonbuild/modules/cmake.py @mensinda +/mesonbuild/modules/unstable_external_project.py @xclaesse /mesonbuild/ast/* @mensinda /mesonbuild/cmake/* @mensinda diff --git a/docs/markdown/External-Project-module.md b/docs/markdown/External-Project-module.md new file mode 100644 index 0000000..54b248f --- /dev/null +++ b/docs/markdown/External-Project-module.md @@ -0,0 +1,116 @@ +# External Project module + +**Note**: the functionality of this module is governed by [Meson's + rules on mixing build systems](Mixing-build-systems.md). + +*This is an experimental module, API could change.* + +This module allows building code that uses build systems other than Meson. This +module is intended to be used to build Autotools subprojects as fallback if the +dependency couldn't be found on the system (e.g. too old distro version). + +The project will be compiled out-of-tree inside Meson's build directory. The +project will also be installed inside Meson's build directory using make's +[`DESTDIR`](https://www.gnu.org/prep/standards/html_node/DESTDIR.html) +feature. During project installation step, that DESTDIR will be copied verbatim +into the desired location. + +External subprojects can use libraries built by Meson (main project, or other +subprojects) using pkg-config, thanks to `*-uninstalled.pc` files generated by +[`pkg.generate()`](Pkgconfig-module.md). + +External build system requirements: +- Must support out-of-tree build. The configure script will be invoked with the + current workdir inside Meson's build directory and not subproject's top source + directory. +- Configure script must generate a `Makefile` in the current workdir. +- Configure script must take common directories like prefix, libdir, etc, as + command line arguments. +- Configure script must support common environment variable like CFLAGS, CC, etc. +- Compilation step must detect when a reconfigure is needed, and do it + transparently. + +Known limitations: +- Executables from external projects cannot be used uninstalled, because they + would need its libraries to be installed in the final location. This is why + there is no `find_program()` method. +- The configure script must generate a `Makefile`, other build systems are not + yet supported. +- When cross compiling, if `PKG_CONFIG_SYSROOT_DIR` is set in environment or + `sys_root` in the cross file properties, the external subproject will not be + able to find dependencies built by meson using pkg-config. The reason is + pkg-config and pkgconf both prepend the sysroot path to `-I` and `-L` arguments + from `-uninstalled.pc` files. This is arguably a bug that could be fixed in + future version of pkg-config/pkgconf. + +*Added 0.56.0* + +## Functions + +### `add_project()` + +This function should be called at the root directory of a project using another +build system. Usually in a `meson.build` file placed in the top directory of a +subproject, but could be also in any subdir. + +Its first positional argument is the name of the configure script to be +executed (e.g. `configure` or `autogen.sh`), that file must be in the current +directory and executable. + +Keyword arguments: +- `configure_options`: An array of strings to be passed as arguments to the + configure script. Some special tags will be replaced by Meson before passing + them to the configure script: `@PREFIX@`, `@LIBDIR@` and `@INCLUDEDIR@`. + Note that `libdir` and `includedir` paths are relative to `prefix` in Meson + but some configure scripts requires absolute path, in that case they can be + passed as `'--libdir=@PREFIX@/@LIBDIR@'`. +- `cross_configure_options`: Extra options appended to `configure_options` only + when cross compiling. special tag `@HOST@` will be replaced by + `'{}-{}-{}'.format(host_machine.cpu_family(), build_machine.system(), host_machine.system()`. + If omitted it defaults to `['--host=@HOST@']`. +- `verbose`: If set to `true` the output of sub-commands ran to configure, build + and install the project will be printed onto Meson's stdout. +- `env` : environment variables to set, such as `['NAME1=value1', 'NAME2=value2']`, + a dictionary, or an [`environment()` object](Reference-manual.md#environment-object). + +Returns an [`ExternalProject`](#ExternalProject_object) object + +## `ExternalProject` object + +### Methods + +#### `dependency(libname)` + +Return a dependency object that can be used to build targets against a library +from the external project. + +Keyword arguments: +- `subdir` path relative to `includedir` to be added to the header search path. + +## Example `meson.build` file for a subproject + +```meson +project('My Autotools Project', 'c', + meson_version : '>=0.56.0', +) + +mod = import('unstable_external_project') + +p = mod.add_project('configure', + configure_options : ['--prefix=@PREFIX@', + '--libdir=@LIBDIR@', + '--incdir=@INCLUDEDIR@', + '--enable-foo', + ], +) + +mylib_dep = p.dependency('mylib') +``` + +## Using wrap file + +Most of the time the project will be built as a subproject, and fetched using +a `.wrap` file. In that case the simple `meson.build` file needed to build the +subproject can be provided by adding `patch_directory=mysubproject` line +in the wrap file, and place the build definition file at +`subprojects/packagefiles/mysubproject/meson.build`. diff --git a/docs/markdown/snippets/external_project.md b/docs/markdown/snippets/external_project.md new file mode 100644 index 0000000..0ecaac8 --- /dev/null +++ b/docs/markdown/snippets/external_project.md @@ -0,0 +1,24 @@ +## External projects + +A new experimental module `unstable_external_project` has been added to build +code using other build systems than Meson. Currently only supporting projects +with a configure script that generates Makefiles. + +```meson +project('My Autotools Project', 'c', + meson_version : '>=0.56.0', +) + +mod = import('unstable_external_project') + +p = mod.add_project('configure', + configure_options : ['--prefix=@PREFIX@', + '--libdir=@LIBDIR@', + '--incdir=@INCLUDEDIR@', + '--enable-foo', + ], +) + +mylib_dep = p.dependency('mylib') +``` + diff --git a/docs/sitemap.txt b/docs/sitemap.txt index ac74870..bdded3e 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -50,6 +50,7 @@ index.md Windows-module.md Cuda-module.md Keyval-module.md + External-Project-module.md Java.md Vala.md D.md diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index c97bcc6..c4bd7c2 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -96,6 +96,9 @@ cflags_mapping = {'c': 'CFLAGS', 'vala': 'VALAFLAGS', 'rust': 'RUSTFLAGS'} +cexe_mapping = {'c': 'CC', + 'cpp': 'CXX'} + # All these are only for C-linkable languages; see `clink_langs` above. def sort_clink(lang): diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 69b81d5..8d2f759 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -642,28 +642,35 @@ class PkgConfigDependency(ExternalDependency): mlog.debug("Called `{}` -> {}\n{}".format(call, rc, out)) return rc, out, err - def _call_pkgbin(self, args, env=None): - # Always copy the environment since we're going to modify it - # with pkg-config variables - if env is None: - env = os.environ.copy() - else: - env = env.copy() - - extra_paths = self.env.coredata.builtins_per_machine[self.for_machine]['pkg_config_path'].value - sysroot = self.env.properties[self.for_machine].get_sys_root() + @staticmethod + def setup_env(env, environment, for_machine, extra_path=None): + extra_paths = environment.coredata.builtins_per_machine[for_machine]['pkg_config_path'].value + if extra_path: + extra_paths.append(extra_path) + sysroot = environment.properties[for_machine].get_sys_root() if sysroot: env['PKG_CONFIG_SYSROOT_DIR'] = sysroot new_pkg_config_path = ':'.join([p for p in extra_paths]) mlog.debug('PKG_CONFIG_PATH: ' + new_pkg_config_path) env['PKG_CONFIG_PATH'] = new_pkg_config_path - pkg_config_libdir_prop = self.env.properties[self.for_machine].get_pkg_config_libdir() + pkg_config_libdir_prop = environment.properties[for_machine].get_pkg_config_libdir() if pkg_config_libdir_prop: new_pkg_config_libdir = ':'.join([p for p in pkg_config_libdir_prop]) env['PKG_CONFIG_LIBDIR'] = new_pkg_config_libdir mlog.debug('PKG_CONFIG_LIBDIR: ' + new_pkg_config_libdir) + + def _call_pkgbin(self, args, env=None): + # Always copy the environment since we're going to modify it + # with pkg-config variables + if env is None: + env = os.environ.copy() + else: + env = env.copy() + + PkgConfigDependency.setup_env(env, self.env, self.for_machine) + fenv = frozenset(env.items()) targs = tuple(args) cache = PkgConfigDependency.pkgbin_cache diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 3c562f3..6a813b8 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -339,6 +339,7 @@ class BinaryTable: 'cmake': 'CMAKE', 'qmake': 'QMAKE', 'pkgconfig': 'PKG_CONFIG', + 'make': 'MAKE', } # type: T.Dict[str, str] # Deprecated environment variables mapped from the new variable to the old one diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 99a2916..bc162ac 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2505,6 +2505,8 @@ class Interpreter(InterpreterBase): return ExternalProgramHolder(item, self.subproject) elif hasattr(item, 'held_object'): return item + elif isinstance(item, InterpreterObject): + return item else: raise InterpreterException('Module returned a value of unknown type.') @@ -2530,6 +2532,8 @@ class Interpreter(InterpreterBase): # FIXME: This is special cased and not ideal: # The first source is our new VapiTarget, the rest are deps self.process_new_values(v.sources[0]) + elif isinstance(v, InstallDir): + self.build.install_dirs.append(v) elif hasattr(v, 'held_object'): pass elif isinstance(v, (int, str, bool, Disabler)): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 943aa42..c6cbbd6 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -971,6 +971,17 @@ def do_define(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', v else: raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) +def get_variable_regex(variable_format: str = 'meson') -> T.Pattern[str]: + # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define + # Also allow escaping '@' with '\@' + if variable_format in ['meson', 'cmake@']: + regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + elif variable_format == 'cmake': + regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}') + else: + raise MesonException('Format "{}" not handled'.format(variable_format)) + return regex + def do_conf_str (data: list, confdata: 'ConfigurationData', variable_format: str, encoding: str = 'utf-8') -> T.Tuple[T.List[str],T.Set[str], bool]: def line_is_valid(line : str, variable_format: str) -> bool: @@ -982,14 +993,7 @@ def do_conf_str (data: list, confdata: 'ConfigurationData', variable_format: str return False return True - # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define - # Also allow escaping '@' with '\@' - if variable_format in ['meson', 'cmake@']: - regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') - elif variable_format == 'cmake': - regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}') - else: - raise MesonException('Format "{}" not handled'.format(variable_format)) + regex = get_variable_regex(variable_format) search_token = '#mesondefine' if variable_format != 'meson': diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py new file mode 100644 index 0000000..ff4685d --- /dev/null +++ b/mesonbuild/modules/unstable_external_project.py @@ -0,0 +1,253 @@ +# 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. + +import os, subprocess, shlex +from pathlib import Path + +from . import ExtensionModule, ModuleReturnValue +from .. import mlog, build +from ..mesonlib import (MesonException, Popen_safe, MachineChoice, + get_variable_regex, do_replacement) +from ..interpreterbase import InterpreterObject, InterpreterException, FeatureNew +from ..interpreterbase import stringArgs, permittedKwargs +from ..interpreter import DependencyHolder, InstallDir +from ..compilers.compilers import cflags_mapping, cexe_mapping +from ..dependencies.base import InternalDependency, PkgConfigDependency + +class ExternalProject(InterpreterObject): + def __init__(self, interpreter, subdir, project_version, subproject, environment, build_machine, host_machine, + configure_command, configure_options, cross_configure_options, env, verbose): + InterpreterObject.__init__(self) + self.methods.update({'dependency': self.dependency_method, + }) + + self.interpreter = interpreter + self.subdir = Path(subdir) + self.project_version = project_version + self.subproject = subproject + self.env = environment + self.build_machine = build_machine + self.host_machine = 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.name = self.subdir.name + 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_builtin_option('prefix')) + self.libdir = Path(self.env.coredata.get_builtin_option('libdir')) + self.includedir = Path(self.env.coredata.get_builtin_option('includedir')) + + # 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.make = self.interpreter.find_program_impl('make') + self.make = self.make.get_command()[0] + + self._configure() + + self.targets = self._create_targets() + + def _configure(self): + # 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 = self.interpreter.find_program_impl(configure_path.as_posix()) + configure_cmd = configure_prog.get_command() + + d = {'PREFIX': self.prefix.as_posix(), + 'LIBDIR': self.libdir.as_posix(), + 'INCLUDEDIR': self.includedir.as_posix(), + } + self._validate_configure_options(d.keys()) + + 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': host} + configure_cmd += self._format_options(self.cross_configure_options, d) + + # Set common env variables like CFLAGS, CC, etc. + link_exelist = [] + link_args = [] + 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) + 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) + if link_exelist: + self.run_env['LD'] = self._quote_and_join(link_exelist) + self.run_env['LDFLAGS'] = self._quote_and_join(link_args) + + self.run_env = self.user_env.get_env(self.run_env) + + PkgConfigDependency.setup_env(self.run_env, self.env, MachineChoice.HOST, + Path(self.env.get_build_dir(), 'meson-uninstalled').as_posix()) + + self.build_dir.mkdir(parents=True, exist_ok=True) + self._run('configure', configure_cmd) + + def _quote_and_join(self, array): + return ' '.join([shlex.quote(i) for i in array]) + + def _validate_configure_options(self, required_keys): + # Ensure the user at least try to pass basic info to the build system, + # like the prefix, libdir, etc. + for key in required_keys: + key_format = '@{}@'.format(key) + for option in self.configure_options: + if key_format in option: + break + else: + m = 'At least one configure option must contain "{}" key' + raise InterpreterException(m.format(key_format)) + + def _format_options(self, options, variables): + out = [] + missing = set() + regex = get_variable_regex('meson') + confdata = {k: (v, None) for k, v in variables.items()} + 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(map(repr, sorted(missing))) + raise EnvironmentException( + "Variables {} in configure options are missing.".format(var_list)) + return out + + def _run(self, step, command): + mlog.log('External project {}:'.format(self.name), mlog.bold(step)) + output = None if self.verbose else subprocess.DEVNULL + p, o, e = Popen_safe(command, cwd=str(self.build_dir), env=self.run_env, + stderr=subprocess.STDOUT, + stdout=output) + if p.returncode != 0: + m = '{} step failed:\n{}'.format(step, e) + raise MesonException(m) + + def _create_targets(self): + 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(), + '--make', self.make, + ] + if self.verbose: + cmd.append('--verbose') + + target_kwargs = {'output': '{}.stamp'.format(self.name), + 'depfile': '{}.d'.format(self.name), + 'command': cmd + ['@OUTPUT@', '@DEPFILE@'], + 'console': True, + } + self.target = build.CustomTarget(self.name, + self.subdir.as_posix(), + self.subproject, + target_kwargs) + + idir = InstallDir(self.subdir.as_posix(), + Path('dist', self.rel_prefix).as_posix(), + install_dir='.', + install_mode=None, + exclude=None, + strip_directory=True, + from_source_dir=False) + + return [self.target, idir] + + @stringArgs + @permittedKwargs({'subdir'}) + def dependency_method(self, args, kwargs): + if len(args) != 1: + m = 'ExternalProject.dependency takes exactly 1 positional arguments' + raise InterpreterException(m) + libname = args[0] + + subdir = kwargs.get('subdir', '') + if not isinstance(subdir, str): + m = 'ExternalProject.dependency subdir keyword argument must be string.' + raise InterpreterException(m) + + abs_includedir = Path(self.install_dir, self.rel_prefix, self.includedir) + if subdir: + abs_includedir = Path(abs_includedir, subdir) + abs_libdir = Path(self.install_dir, self.rel_prefix, self.libdir) + + version = self.project_version['version'] + incdir = [] + compile_args = ['-I{}'.format(abs_includedir)] + link_args = ['-L{}'.format(abs_libdir), '-l{}'.format(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) + return DependencyHolder(dep, self.subproject) + + +class ExternalProjectModule(ExtensionModule): + @FeatureNew('External build system Module', '0.56.0') + def __init__(self, interpreter): + super().__init__(interpreter) + + @stringArgs + @permittedKwargs({'configure_options', 'cross_configure_options', 'verbose', 'env'}) + def add_project(self, state, args, kwargs): + if len(args) != 1: + raise InterpreterException('add_project takes exactly one positional argument') + configure_command = args[0] + configure_options = kwargs.get('configure_options', []) + cross_configure_options = kwargs.get('cross_configure_options', ['--host={host}']) + verbose = kwargs.get('verbose', False) + env = self.interpreter.unpack_env_kwarg(kwargs) + project = ExternalProject(self.interpreter, + state.subdir, + state.project_version, + state.subproject, + state.environment, + state.build_machine, + state.host_machine, + configure_command, + configure_options, + cross_configure_options, + env, verbose) + return ModuleReturnValue(project, project.targets) + + +def initialize(*args, **kwargs): + return ExternalProjectModule(*args, **kwargs) diff --git a/mesonbuild/scripts/externalproject.py b/mesonbuild/scripts/externalproject.py new file mode 100644 index 0000000..6c3a89c --- /dev/null +++ b/mesonbuild/scripts/externalproject.py @@ -0,0 +1,95 @@ +# Copyright 2019 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. + +import os +import argparse +import multiprocessing +import subprocess +from pathlib import Path + +from ..mesonlib import Popen_safe + +class ExternalProject: + def __init__(self, options): + self.name = options.name + self.src_dir = options.srcdir + self.build_dir = options.builddir + self.install_dir = options.installdir + self.verbose = options.verbose + self.stampfile = options.stampfile + self.depfile = options.depfile + self.make = options.make + + def write_depfile(self): + with open(self.depfile, 'w') as f: + f.write('{}: \\\n'.format(self.stampfile)) + for dirpath, dirnames, filenames in os.walk(self.src_dir): + dirnames[:] = [d for d in dirnames if not d.startswith('.')] + for fname in filenames: + if fname.startswith('.'): + continue + path = Path(dirpath, fname) + f.write(' {} \\\n'.format(path.as_posix().replace(' ', '\\ '))) + + def write_stampfile(self): + with open(self.stampfile, 'w') as f: + pass + + def gnu_make(self): + p, o, e = Popen_safe([self.make, '--version']) + if p.returncode == 0 and 'GNU Make' in o: + return True + return False + + def build(self): + make_cmd = [self.make] + if not self.verbose: + make_cmd.append('--quiet') + if self.gnu_make(): + make_cmd.append('-j' + str(multiprocessing.cpu_count())) + + rc = self._run(make_cmd) + if rc != 0: + return rc + + install_cmd = make_cmd + ['DESTDIR= ' + self.install_dir, 'install'] + rc = self._run(install_cmd) + if rc != 0: + return rc + + self.write_depfile() + self.write_stampfile() + + return 0 + + def _run(self, command): + output = None if self.verbose else subprocess.DEVNULL + p, o, e = Popen_safe(command, stderr=subprocess.STDOUT, stdout=output, + cwd=self.build_dir) + return p.returncode + +def run(args): + parser = argparse.ArgumentParser() + parser.add_argument('--name') + parser.add_argument('--srcdir') + parser.add_argument('--builddir') + parser.add_argument('--installdir') + parser.add_argument('--make') + parser.add_argument('--verbose', action='store_true') + parser.add_argument('stampfile') + parser.add_argument('depfile') + + options = parser.parse_args(args) + ep = ExternalProject(options) + return ep.build() diff --git a/test cases/common/236 external project/app.c b/test cases/common/236 external project/app.c new file mode 100644 index 0000000..166f007 --- /dev/null +++ b/test cases/common/236 external project/app.c @@ -0,0 +1,7 @@ +#include <libfoo.h> + +int main(void) +{ + return call_foo() == 42 ? 0 : 1; +} + diff --git a/test cases/common/236 external project/func.c b/test cases/common/236 external project/func.c new file mode 100644 index 0000000..5e8f933 --- /dev/null +++ b/test cases/common/236 external project/func.c @@ -0,0 +1,7 @@ +#include "func.h" + +int func(void) +{ + return 1; +} + diff --git a/test cases/common/236 external project/func.h b/test cases/common/236 external project/func.h new file mode 100644 index 0000000..340b82a --- /dev/null +++ b/test cases/common/236 external project/func.h @@ -0,0 +1 @@ +int func(void); diff --git a/test cases/common/236 external project/libfoo/configure b/test cases/common/236 external project/libfoo/configure new file mode 100755 index 0000000..a867b48 --- /dev/null +++ b/test cases/common/236 external project/libfoo/configure @@ -0,0 +1,44 @@ +#! /bin/sh + +srcdir=$(dirname "$0") + +for i in "$@" +do +case $i in + --prefix=*) + PREFIX="${i#*=}" + shift + ;; + --libdir=*) + LIBDIR="${i#*=}" + shift + ;; + --includedir=*) + INCDIR="${i#*=}" + shift + ;; + --libext=*) + LIBEXT="${i#*=}" + shift + ;; + *) + shift + ;; +esac +done + +DEP_ARGS=$(pkg-config somelib --cflags --libs) + +cat > Makefile << EOL +all: libfoo.$LIBEXT + +libfoo.$LIBEXT: + $CC "$srcdir/libfoo.c" -shared -fPIC $DEP_ARGS -o \$@ + +install: libfoo.$LIBEXT + mkdir -p "\$(DESTDIR)$LIBDIR"; + mkdir -p "\$(DESTDIR)$LIBDIR/pkgconfig"; + mkdir -p "\$(DESTDIR)$INCDIR"; + cp \$< "\$(DESTDIR)$LIBDIR"; + cp "$srcdir/libfoo.h" "\$(DESTDIR)$INCDIR"; +EOL diff --git a/test cases/common/236 external project/libfoo/libfoo.c b/test cases/common/236 external project/libfoo/libfoo.c new file mode 100644 index 0000000..3f62282 --- /dev/null +++ b/test cases/common/236 external project/libfoo/libfoo.c @@ -0,0 +1,8 @@ +#include "libfoo.h" + +int func(void); + +int call_foo() +{ + return func() == 1 ? 42 : 0; +} diff --git a/test cases/common/236 external project/libfoo/libfoo.h b/test cases/common/236 external project/libfoo/libfoo.h new file mode 100644 index 0000000..8981f18 --- /dev/null +++ b/test cases/common/236 external project/libfoo/libfoo.h @@ -0,0 +1,3 @@ +#pragma once + +int call_foo(void); diff --git a/test cases/common/236 external project/libfoo/meson.build b/test cases/common/236 external project/libfoo/meson.build new file mode 100644 index 0000000..941e13f --- /dev/null +++ b/test cases/common/236 external project/libfoo/meson.build @@ -0,0 +1,22 @@ +mod = import('unstable_external_project') + +target_system = target_machine.system() +if target_system in ['windows', 'cygwin'] + libext = 'dll' +elif target_system == 'darwin' + libext = 'dylib' +else + libext = 'so' +endif + +p = mod.add_project('configure', + configure_options : [ + '--prefix=@PREFIX@', + '--libdir=@PREFIX@/@LIBDIR@', + '--includedir=@PREFIX@/@INCLUDEDIR@', + '--libext=' + libext, + ], +) + +libfoo_dep = declare_dependency(link_with : somelib, + dependencies : p.dependency('foo')) diff --git a/test cases/common/236 external project/meson.build b/test cases/common/236 external project/meson.build new file mode 100644 index 0000000..d1ed797 --- /dev/null +++ b/test cases/common/236 external project/meson.build @@ -0,0 +1,27 @@ +project('test external project', 'c') + +if not find_program('pkg-config', required: false).found() + error('MESON_SKIP_TEST: pkg-config not found') +endif + +if not find_program('make', required : false).found() + error('MESON_SKIP_TEST: make not found') +endif + +if host_machine.system() == 'windows' + error('MESON_SKIP_TEST: The fake configure script is too dumb to work on Windows') +endif + +if meson.is_cross_build() + # CI uses PKG_CONFIG_SYSROOT_DIR which breaks -uninstalled.pc usage. + error('MESON_SKIP_TEST: Cross build support is too limited for this test') +endif + +pkg = import('pkgconfig') + +somelib = library('somelib', 'func.c') +pkg.generate(somelib) + +subdir('libfoo') + +executable('test-find-library', 'app.c', dependencies : libfoo_dep) diff --git a/test cases/common/236 external project/test.json b/test cases/common/236 external project/test.json new file mode 100644 index 0000000..4888e87 --- /dev/null +++ b/test cases/common/236 external project/test.json @@ -0,0 +1,7 @@ +{ + "installed": [ + { "type": "shared_lib", "file": "usr/lib/foo" }, + { "type": "file", "file": "usr/include/libfoo.h" }, + { "type": "file", "file": "usr/lib/pkgconfig/somelib.pc" } + ] +} |