aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/modules
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2018-10-04 00:07:44 -0400
committerXavier Claessens <xclaesse@gmail.com>2020-09-13 13:54:47 -0400
commit9d338200dacdf24c50259c309380200f8a53d5b5 (patch)
tree8e268a9357119265f11b30791f56e8e09fec393e /mesonbuild/modules
parent19696c3dcdb379f4c5b88457f43242f2d33427a0 (diff)
downloadmeson-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
Diffstat (limited to 'mesonbuild/modules')
-rw-r--r--mesonbuild/modules/unstable_external_project.py253
1 files changed, 253 insertions, 0 deletions
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)