diff options
author | Nirbheek Chauhan <nirbheek@centricular.com> | 2017-11-12 22:32:33 +0530 |
---|---|---|
committer | Nirbheek Chauhan <nirbheek@centricular.com> | 2017-12-16 19:22:18 +0530 |
commit | 580c8512c17154a7a18bb1e3853f4c9e9171490e (patch) | |
tree | 0a87f68c2154aeb90d5dc34c3b5948057a3039b5 | |
parent | b3b3a7d9331589d69afc4b8eb7f9fbef96b8cf67 (diff) | |
download | meson-nirbheek/cargo-module.tar.gz |
WIP: Add an unstable module for Rust Cargo supportnirbheek/cargo-module
Currently only supports building staticlib libraries and binaries with
Cargo. All configuration must be in the Cargo.toml file.
You should use cargo 0.23 because it fixes a bug in emitting dep-info
FIXMEs:
* Cross compilation is broken. We do not pass the `--target TRIPLE` flag
to cargo, so it always builds targetting the build machine.
* tests and benches are currently not supported, but can be added
* cdylibs are not supported because it is not clear if anyone uses them
and if they even work properly in Rust
* `ninja dist` does not yet run `cargo vendor` to add crate sources
* `cargo clean` is not called on `ninja clean`
* We cannot handle adding system libraries that are needed by the
staticlib while linking because it's outputted at build time
by `cargo build`. You must handle that yourself.
* We do not pass on RUSTFLAGS from the env during configure to cargo
build.
-rw-r--r-- | mesonbuild/mesonlib.py | 2 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_cargo.py | 307 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/.gitignore | 1 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/bins/Cargo.toml | 6 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/bins/meson.build | 5 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/bins/src/main.rs | 3 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/bothlibs/Cargo.toml | 9 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/bothlibs/meson.build | 3 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/bothlibs/src/lib.rs | 7 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/installed_files.txt | 5 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/meson.build | 7 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/staticlib/Cargo.toml | 9 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/staticlib/meson.build | 3 | ||||
-rw-r--r-- | test cases/rust/7 cargo module/staticlib/src/lib.rs | 7 |
14 files changed, 374 insertions, 0 deletions
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 9ad066821..cb8ad8734 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -661,6 +661,8 @@ def expand_arguments(args): return None return expended_args +Popen_safe_errors = (FileNotFoundError, PermissionError, subprocess.CalledProcessError) + def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): if sys.version_info < (3, 6) or not sys.stdout.encoding: return Popen_safe_legacy(args, write=write, stderr=stderr, **kwargs) diff --git a/mesonbuild/modules/unstable_cargo.py b/mesonbuild/modules/unstable_cargo.py new file mode 100644 index 000000000..096efcb7b --- /dev/null +++ b/mesonbuild/modules/unstable_cargo.py @@ -0,0 +1,307 @@ +# Copyright 2017 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 json + +from .. import mlog +from ..mesonlib import Popen_safe, Popen_safe_errors +from ..mesonlib import extract_as_list, version_compare, MesonException, File +from ..environment import for_windows, for_cygwin, for_darwin +from ..interpreterbase import noKwargs, permittedKwargs +from ..dependencies import InternalDependency +from ..build import CustomTarget + +from . import ExtensionModule, ModuleReturnValue, permittedSnippetKwargs + +target_kwargs = {'toml', 'sources', 'install', 'install_dir'} + + +class CargoModule(ExtensionModule): + cargo = ['cargo'] + cargo_build = ['cargo', 'build', '-v', '--color=always'] + cargo_version = None + + def __init__(self): + super().__init__() + try: + self._get_version() + except Popen_safe_errors: + raise MesonException('Cargo was not found') + self.snippets.add('test') + self.snippets.add('benchmark') + + def _get_cargo_build(self, toml): + # FIXME: must use target-triple to set the host system, currently it + # always builds for the build machine. + return self.cargo_build + ['--manifest-path', toml] + + def _is_release(self, env): + buildtype = env.coredata.get_builtin_option('buildtype') + return not buildtype.startswith('debug') + + def _get_crate_name(self, name, crate_type, env): + ''' + We have no control over what filenames cargo uses for its output, so we + have to figure it out ourselves. + ''' + if for_cygwin(env.is_cross_build(), env): + raise MesonException('Cygwin cargo support is TODO') + prefix = 'lib' + if crate_type == 'staticlib': + suffix = 'a' + if for_windows(env.is_cross_build(), env): + prefix = '' + suffix = 'lib' + elif crate_type == 'cdylib': + if for_windows(env.is_cross_build(), env): + prefix = '' + suffix = 'dll' + elif for_darwin(env.is_cross_build(), env): + suffix = 'dylib' + else: + suffix = 'so' + elif crate_type == 'bin': + prefix = '' + suffix = '' + if for_windows(env.is_cross_build(), env): + suffix = 'exe' + if suffix: + fname = prefix + name + '.' + suffix + else: + fname = prefix + name + if self._is_release(env): + return os.path.join('release', fname) + else: + return os.path.join('debug', fname) + + def _read_metadata(self, toml): + cmd = self.cargo + ['metadata', '--format-version=1', '--no-deps', + '--manifest-path', toml] + out = Popen_safe(cmd)[1] + try: + encoded = json.loads(out) + except json.decoder.JSONDecodeError: + print(cmd, out) + raise + return encoded['packages'][0] + + def _source_strings_to_files(self, source_dir, subdir, sources): + results = [] + for s in sources: + if isinstance(s, File): + pass + elif isinstance(s, str): + s = File.from_source_file(source_dir, subdir, s) + else: + raise MesonException('Source item is {!r} instead of ' + 'string or files() object'.format(s)) + results.append(s) + return results + + def _get_sources(self, state, kwargs): + # 'sources' kwargs is optional; we have a depfile with dependency + # information and ninja will use that to determine when to rebuild. + sources = extract_as_list(kwargs, 'sources') + return self._source_strings_to_files(state.environment.source_dir, + state.subdir, sources) + + def _get_cargo_test_outputs(self, name, metadata, env): + args = [] + outputs = [] + depfile = None + for t in metadata['targets']: + if t['name'] != name: + continue + # Filter out crate types we don't want + # a test target will only have one output + if t['crate_types'] != ['bin']: + continue + # Filter out the target `kind`s that we don't want + if t['kind'] != ['test']: + continue + outputs.append(self._get_crate_name(name, 'bin', env)) + args = ['--test', name] + break + if outputs: + depfile = os.path.splitext(outputs[0])[0] + '.d' + else: + toml = metadata['manifest_path'] + raise MesonException('no test called {!r} found in {!r}' + ''.format(name, toml)) + return outputs, depfile, args + + def _get_cargo_executable_outputs(self, name, metadata, env): + args = [] + outputs = [] + depfile = None + for t in metadata['targets']: + if t['name'] != name: + continue + # Filter out crate types we don't want + # an executable target will only have one output + if t['crate_types'] != ['bin']: + continue + # Filter out the target `kind`s that we don't want + if t['kind'] not in [['example'], ['bin']]: + continue + outputs.append(self._get_crate_name(name, 'bin', env)) + if t['kind'][0] == 'example': + args = ['--example', name] + else: + args = ['--bin', name] + break + if outputs: + depfile = os.path.splitext(outputs[0])[0] + '.d' + else: + toml = metadata['manifest_path'] + raise MesonException('no bin called {!r} found in {!r}' + ''.format(name, toml)) + return outputs, depfile, args + + def _get_cargo_static_library_outputs(self, name, metadata, env): + args = [] + outputs = [] + depfile = None + for t in metadata['targets']: + if t['name'] != name: + continue + # Filter out the target `kind`s that we don't want + # a library target can have multiple outputs + if 'staticlib' not in t['kind'] and \ + 'example' not in t['kind']: + continue + for ct in t['crate_types']: + if ct == 'staticlib': + outputs.append(self._get_crate_name(name, ct, env)) + if t['kind'][0] == 'example': + # If the library is an example, it must be built by name + args = ['--example', name] + else: + # Library is the crate itself, no name needed + args = ['--lib'] + break + if outputs: + depfile = os.path.splitext(outputs[0])[0] + '.d' + else: + toml = metadata['manifest_path'] + raise MesonException('no staticlib called {!r} found ' + 'in {!r}'.format(name, toml)) + return outputs, depfile, args + + def _get_cargo_outputs(self, name, metadata, env, cargo_target_type): + # FIXME: track which outputs have already been fetched from + # a toml file and disallow duplicates. + fn = getattr(self, '_get_cargo_{}_outputs'.format(cargo_target_type)) + return fn(name, metadata, env) + + def _check_cargo_dep_info_bug(self, metadata): + if version_compare(self.cargo_version, '>0.22.0'): + return + for t in metadata['targets']: + if t['kind'] == ['custom-build']: + m = 'Crate {!r} contains a custom build script {!r} which ' \ + 'will cause dep-info to not being emitted due to a ' \ + 'bug in Cargo. Please upgrade to Cargo 0.23 or newer.' \ + ''.format(metadata['name'], os.path.basename(t['src_path'])) + mlog.warning(m) + return + + def _cargo_target(self, state, args, kwargs, cargo_target_type): + ctkwargs = {} + env = state.environment + if len(args) != 1: + raise MesonException('{0}() requires exactly one positional ' + 'argument: the name of the {0}' + ''.format(cargo_target_type)) + name = args[0] + if 'toml' not in kwargs: + raise MesonException('"toml" kwarg is required') + toml = File.from_source_file(env.get_source_dir(), state.subdir, + kwargs['toml']) + # Get the Cargo.toml file as a JSON encoded object + md = self._read_metadata(toml.absolute_path(env.source_dir, None)) + # Warn about the cargo dep-info bug if needed + self._check_cargo_dep_info_bug(md) + # Get the list of outputs that cargo will create matching the specified name + ctkwargs['output'], ctkwargs['depfile'], cargo_args = \ + self._get_cargo_outputs(name, md, env, cargo_target_type) + # Set the files that will trigger a rebuild + ctkwargs['depend_files'] = [toml] + self._get_sources(state, kwargs) + # Cargo command that will build the output library/libraries/bins + cmd = self._get_cargo_build(toml) + cargo_args + if self._is_release(env): + cmd.append('--release') + ctkwargs['command'] = cmd + if 'install' in kwargs: + ctkwargs['install'] = kwargs['install'] + if 'install_dir' in kwargs: + ctkwargs['install_dir'] = kwargs['install_dir'] + elif 'install' in kwargs: + # People should be able to set `install: true` and get a good + # default for `install_dir` + if cargo_target_type == 'static_library': + ctkwargs['install_dir'] = env.coredata.get_builtin_option('libdir') + elif cargo_target_type == 'executable': + ctkwargs['install_dir'] = env.coredata.get_builtin_option('bindir') + ct = CustomTarget(name, state.subdir, state.subproject, ctkwargs) + # Ninja buffers all cargo output so we get no status updates + ct.ninja_pool = 'console' + # Force it to output in the current directory + ct.envvars['CARGO_TARGET_DIR'] = state.subdir + # XXX: we need to call `cargo clean` on `ninja clean`. + return md, ct + + @permittedKwargs(target_kwargs) + def static_library(self, state, args, kwargs): + md, ct = self._cargo_target(state, args, kwargs, 'static_library') + # XXX: Cargo build outputs a list of system libraries that are needed + # by this (possibly) static library, but we have no way of accessing it + # during configure. So we require developers to manage that themselves. + # XXX: We add the output file into `sources`, but that creates + # a compile-time dependency instead of a link-time dependency and + # reduces parallelism. + d = InternalDependency(md['version'], [], [], [], [], [ct], []) + return ModuleReturnValue(d, [d]) + + @permittedKwargs(target_kwargs) + def executable(self, state, args, kwargs): + md, ct = self._cargo_target(state, args, kwargs, 'executable') + # XXX: We return a custom target, which means this may not be usable + # everywhere that an executable build target can be. + return ModuleReturnValue(ct, [ct]) + + @permittedSnippetKwargs(target_kwargs) + def test(self, interpreter, state, args, kwargs): + # This would map to cargo tests + raise MesonException('Not implemented') + + @permittedSnippetKwargs(target_kwargs) + def benchmark(self, interpreter, state, args, kwargs): + # This would map to cargo benches + raise MesonException('Not implemented') + + def _get_version(self): + if self.cargo_version is None: + out = Popen_safe(self.cargo + ['--version'])[1] + self.cargo_version = out.strip().split('cargo ')[1] + return self.cargo_version + + @noKwargs + def version(self, state, args, kwargs): + return ModuleReturnValue(self._get_version(), []) + + +def initialize(): + return CargoModule() diff --git a/test cases/rust/7 cargo module/.gitignore b/test cases/rust/7 cargo module/.gitignore new file mode 100644 index 000000000..03314f77b --- /dev/null +++ b/test cases/rust/7 cargo module/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/test cases/rust/7 cargo module/bins/Cargo.toml b/test cases/rust/7 cargo module/bins/Cargo.toml new file mode 100644 index 000000000..f3745cdbc --- /dev/null +++ b/test cases/rust/7 cargo module/bins/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mesonbin" +version = "0.1.0" +authors = ["Nirbheek Chauhan <nirbheek@centricular.com>"] + +[dependencies] diff --git a/test cases/rust/7 cargo module/bins/meson.build b/test cases/rust/7 cargo module/bins/meson.build new file mode 100644 index 000000000..b2b9efcdd --- /dev/null +++ b/test cases/rust/7 cargo module/bins/meson.build @@ -0,0 +1,5 @@ +cargo.executable('mesonbin', + toml : 'Cargo.toml', + # This is optional, since ninja keeps track of dependencies on source files. + sources : ['src/main.rs'], + install : true) diff --git a/test cases/rust/7 cargo module/bins/src/main.rs b/test cases/rust/7 cargo module/bins/src/main.rs new file mode 100644 index 000000000..e7a11a969 --- /dev/null +++ b/test cases/rust/7 cargo module/bins/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/test cases/rust/7 cargo module/bothlibs/Cargo.toml b/test cases/rust/7 cargo module/bothlibs/Cargo.toml new file mode 100644 index 000000000..d35932e22 --- /dev/null +++ b/test cases/rust/7 cargo module/bothlibs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "mesonbothlib" +version = "0.1.0" +authors = ["Nirbheek Chauhan <nirbheek@centricular.com>"] + +[dependencies] + +[lib] +crate-type = ['staticlib', 'cdylib'] diff --git a/test cases/rust/7 cargo module/bothlibs/meson.build b/test cases/rust/7 cargo module/bothlibs/meson.build new file mode 100644 index 000000000..4627a3d56 --- /dev/null +++ b/test cases/rust/7 cargo module/bothlibs/meson.build @@ -0,0 +1,3 @@ +cargo.static_library('mesonbothlib', + toml : 'Cargo.toml', + install : true) diff --git a/test cases/rust/7 cargo module/bothlibs/src/lib.rs b/test cases/rust/7 cargo module/bothlibs/src/lib.rs new file mode 100644 index 000000000..31e1bb209 --- /dev/null +++ b/test cases/rust/7 cargo module/bothlibs/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/test cases/rust/7 cargo module/installed_files.txt b/test cases/rust/7 cargo module/installed_files.txt new file mode 100644 index 000000000..117efb430 --- /dev/null +++ b/test cases/rust/7 cargo module/installed_files.txt @@ -0,0 +1,5 @@ +?gcc:usr/lib/libmesonstatic.a +?gcc:usr/lib/libmesonbothlib.a +?msvc:usr/lib/mesonstatic.lib +?msvc:usr/lib/mesonbothlib.lib +usr/bin/mesonbin?exe diff --git a/test cases/rust/7 cargo module/meson.build b/test cases/rust/7 cargo module/meson.build new file mode 100644 index 000000000..dd92bd5ba --- /dev/null +++ b/test cases/rust/7 cargo module/meson.build @@ -0,0 +1,7 @@ +project('cargo module', 'c', 'rust') + +cargo = import('unstable-cargo') + +subdir('staticlib') +subdir('bothlibs') +subdir('bins') diff --git a/test cases/rust/7 cargo module/staticlib/Cargo.toml b/test cases/rust/7 cargo module/staticlib/Cargo.toml new file mode 100644 index 000000000..4e1bfb794 --- /dev/null +++ b/test cases/rust/7 cargo module/staticlib/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "mesonstatic" +version = "0.1.0" +authors = ["Nirbheek Chauhan <nirbheek@centricular.com>"] + +[dependencies] + +[lib] +crate-type = ['staticlib'] diff --git a/test cases/rust/7 cargo module/staticlib/meson.build b/test cases/rust/7 cargo module/staticlib/meson.build new file mode 100644 index 000000000..b2d0f31ae --- /dev/null +++ b/test cases/rust/7 cargo module/staticlib/meson.build @@ -0,0 +1,3 @@ +cargo.static_library('mesonstatic', + toml : 'Cargo.toml', + install : true) diff --git a/test cases/rust/7 cargo module/staticlib/src/lib.rs b/test cases/rust/7 cargo module/staticlib/src/lib.rs new file mode 100644 index 000000000..31e1bb209 --- /dev/null +++ b/test cases/rust/7 cargo module/staticlib/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} |