Skip to content

Commit

Permalink
rust: add new 'object' type crate
Browse files Browse the repository at this point in the history
Allows linking Rust sources in C/C++ programs as static object files, which
means the Rust standard libraries/crates are not statically linked in the program,
saving a huge amount of installation space.
The caller is responsible for providing linkage to the standard library and/or any
needed external crate.
  • Loading branch information
bluca committed Jan 3, 2023
1 parent 0544ffa commit 2562c7b
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 5 deletions.
27 changes: 27 additions & 0 deletions docs/markdown/snippets/rust_object_crates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Rust object crates

In order to link Rust objects into C/C++ libraries/programs without static
linking all crates/libraries used by the objects, the new 'object' type crate
can be used. It will produce object files instead of libraries from the Rust
sources. The caller is responsible for providing the linking parameters for
any crate/library needed by the Rust objects. Note that due to the instability
of Rustc, such object files might require extra work (e.g.: additional linking)
depending on the compiler version, and might change between versions. This
option is provided as-is, and it's the caller's responsibility to deal with
these issues.

```meson
libstd_rust = meson.get_compiler('c').find_library('std-abcdefgh')
librust = static_library(
'rust',
'librust.rs',
rust_crate_type : 'object',
dependencies: libstd-rust
)
user = executable(
'user,
'user.c',
link_with : librust)
```
12 changes: 11 additions & 1 deletion docs/yaml/functions/_build_target_base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,21 @@ kwargs:
If it is a [[static_library]] it defaults to "lib", and may be "lib",
"staticlib", or "rlib". If "lib" then Rustc will pick a default, "staticlib"
means a C ABI library, "rlib" means a Rust ABI.
means a C ABI library, "rlib" means a Rust ABI, "object" means only object
files will be emitted.
If it is a [[shared_library]] it defaults to "lib", and may be "lib",
"dylib", "cdylib", or "proc-macro". If "lib" then Rustc will pick a
default, "cdylib" means a C ABI library, "dylib" means a Rust ABI, and
"proc-macro" is a special rust proceedural macro crate.
"proc-macro" is new in 0.62.0.
"object" is new in 1.1.0 and allows to link Rust object files in C/C++
programs without static linking Rust crates/libraries. The caller must
provide dynamic linking parameters for all crates/libraries used, including
the Rust standard library. Note that due to the instability of Rustc, such
object files might require extra work (e.g.: additional linking)
depending on the compiler version, and might change between versions. This
option is provided as-is, and it's the caller's responsibility to deal with
these issues.
10 changes: 9 additions & 1 deletion mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,14 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
cratetype = 'rlib'
else:
raise InvalidArguments('Unknown target type for rustc.')

# Support for building and linking only object files, without static linking crates
# Note that we don't emit the shared library here (avoid --emit link) as we only want objects
if cratetype == 'object':
cratetype = 'cdylib'
args.extend(['--emit', 'obj', '-C', 'prefer-dynamic'])
else:
args.extend(['--emit', 'link'])
args.extend(['--crate-type', cratetype])

# If we're dynamically linking, add those arguments
Expand All @@ -1884,7 +1892,7 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
# Rustc replaces - with _. spaces are not allowed, so we replace them with underscores
args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_')]
depfile = os.path.join(target.subdir, target.name + '.d')
args += ['--emit', f'dep-info={depfile}', '--emit', 'link']
args += ['--emit', f'dep-info={depfile}']
args += target.get_extra_args('rust')
output = rustc.get_output_args(os.path.join(target.subdir, target.get_filename()))
args += output
Expand Down
14 changes: 12 additions & 2 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1934,8 +1934,14 @@ def post_init(self) -> None:
mlog.debug('Defaulting Rust static library target crate type to rlib')
self.rust_crate_type = 'rlib'
# Don't let configuration proceed with a non-static crate type
elif self.rust_crate_type not in ['rlib', 'staticlib']:
raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for static libraries; must be "rlib" or "staticlib"')
elif self.rust_crate_type not in ['rlib', 'staticlib', 'object']:
raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for static libraries; must be "rlib", "staticlib" or "object"')
elif self.rust_crate_type == 'object':
mlog.warning(textwrap.dedent("""\
Due to the unreliability of Rustc, crate type "object" compiled output might be unstable and require extra work to use.
The use of this feature means that Meson makes no guarantee anything works. We believe it's likely this is going to break without warning in
micro-releases of rust, and if it does, you need to report it to the package making use of it, not Meson, because all bugs are the responsibility of
the package using unstable features."""))
# By default a static library is named libfoo.a even on Windows because
# MSVC does not have a consistent convention for what static libraries
# are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses
Expand All @@ -1952,6 +1958,8 @@ def post_init(self) -> None:
self.suffix = 'rlib'
elif self.rust_crate_type == 'staticlib':
self.suffix = 'a'
elif self.rust_crate_type == 'object':
self.suffix = 'o'
else:
self.suffix = 'a'
self.filename = self.prefix + self.name + '.' + self.suffix
Expand Down Expand Up @@ -2250,6 +2258,8 @@ def process_kwargs(self, kwargs):
raise InvalidArguments(f'Invalid rust_crate_type "{rust_crate_type}": must be a string.')
if rust_crate_type == 'proc-macro':
FeatureNew.single_use('Rust crate type "proc-macro"', '0.62.0', self.subproject)
if rust_crate_type == 'object':
FeatureNew.single_use('Rust crate type "object"', '1.1.0', self.subproject)

def get_import_filename(self) -> T.Optional[str]:
"""
Expand Down
2 changes: 1 addition & 1 deletion test cases/failing/54 wrong static crate type/test.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/54 wrong static crate type/meson.build:7:0: ERROR: Crate type \"cdylib\" invalid for static libraries; must be \"rlib\" or \"staticlib\""
"line": "test cases/failing/54 wrong static crate type/meson.build:7:0: ERROR: Crate type \"cdylib\" invalid for static libraries; must be \"rlib\", \"staticlib\" or \"object\""
}
]
}
8 changes: 8 additions & 0 deletions test cases/rust/20 object link/librust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std::os::raw::c_int;

#[no_mangle]
pub unsafe extern "C" fn print_foo() -> c_int {
let foo = "rust compiler is working";
println!("{}", foo);
0
}
24 changes: 24 additions & 0 deletions test cases/rust/20 object link/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
project('staticprog', 'c')

rustc = find_program('rustc')
add_languages('rust')

libstd_rust = dependency('std-rust', required: false)

librust = static_library(
'rust',
'librust.rs',
dependencies: libstd_rust,
rust_crate_type: 'object')

if libstd_rust.found()
e = executable('c-program', 'prog.c',
link_with: librust,
install : true
)
test('rusttest', e)
else
# Requires a discoverable std library, most likely via pkg-config,
# but there is no common standard so make it optional for now.
error('MESON_SKIP_TEST: libstd-rust dependency not found')
endif
8 changes: 8 additions & 0 deletions test cases/rust/20 object link/prog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <stdio.h>

int print_foo(void);

int main(void) {
print_foo();
return 0;
}

0 comments on commit 2562c7b

Please sign in to comment.