Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 47 additions & 66 deletions mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1962,23 +1962,22 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
except KeyError:
pass

# Since 1.61.0 Rust has a special modifier for whole-archive linking,
# before that it would treat linking two static libraries as
# whole-archive linking. However, to make this work we have to disable
# bundling, which can't be done until 1.63.0… So for 1.61–1.62 we just
# have to hope that the default cases of +whole-archive are sufficient.
# See: https://github.com/rust-lang/rust/issues/99429
if mesonlib.version_compare(rustc.version, '>= 1.63.0'):
whole_archive = '+whole-archive,-bundle'
else:
whole_archive = ''

# FIXME: Seems broken on MacOS: https://github.com/rust-lang/rust/issues/116674
if mesonlib.version_compare(rustc.version, '>= 1.67.0') and not mesonlib.is_osx():
if mesonlib.version_compare(rustc.version, '>= 1.67.0'):
verbatim = '+verbatim'
else:
verbatim = ''

def _link_library(libname: str, static: bool, bundle: bool = False):
type_ = 'static' if static else 'dylib'
modifiers = []
if not bundle and static:
modifiers.append('-bundle')
if verbatim:
modifiers.append(verbatim)
if modifiers:
type_ += ':' + ','.join(modifiers)
args.append(f'-l{type_}={libname}')

linkdirs = mesonlib.OrderedSet()
external_deps = target.external_deps.copy()
target_deps = target.get_dependencies()
Expand All @@ -2001,72 +2000,54 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
if isinstance(d, build.StaticLibrary):
external_deps.extend(d.external_deps)

lib = None
modifiers = []
# Pass native libraries directly to the linker with "-C link-arg"
# because rustc's "-l:+verbatim=" is not portable and we cannot rely
# on linker to find the right library without using verbatim filename.
# For example "-lfoo" won't find "foo.so" in the case name_prefix set
# to "", or would always pick the shared library when both "libfoo.so"
# and "libfoo.a" are available.
# See https://doc.rust-lang.org/rustc/command-line-arguments.html#linking-modifiers-verbatim.
#
# However, rustc static linker (rlib and staticlib) requires using
# "-l" argument and does not rely on platform specific dynamic linker.
lib = self.get_target_filename_for_linking(d)
link_whole = d in target.link_whole_targets
if link_whole and whole_archive:
modifiers.append(whole_archive)
if verbatim:
modifiers.append(verbatim)
lib = self.get_target_filename_for_linking(d)
elif rustc.linker.id in {'link', 'lld-link'} and isinstance(d, build.StaticLibrary):
# Rustc doesn't follow Meson's convention that static libraries
# are called .a, and import libraries are .lib, so we have to
# manually handle that.
if link_whole:
if isinstance(target, build.StaticLibrary):
# If we don't, for static libraries the only option is
# to make a copy, since we can't pass objects in, or
# directly affect the archiver. but we're not going to
# do that given how quickly rustc versions go out of
# support unless there's a compelling reason to do so.
# This only affects 1.61–1.66
mlog.warning('Due to limitations in Rustc versions 1.61–1.66 and meson library naming,',
'whole-archive linking with MSVC may or may not work. Upgrade rustc to',
'>= 1.67. A best effort is being made, but likely won\'t work')
lib = d.name
else:
# When doing dynamic linking (binaries and [c]dylibs),
# we can instead just proxy the correct arguments to the linker
for link_whole_arg in rustc.linker.get_link_whole_for([self.get_target_filename_for_linking(d)]):
args += ['-C', f'link-arg={link_whole_arg}']
else:
args += ['-C', f'link-arg={self.get_target_filename_for_linking(d)}']
if isinstance(target, build.StaticLibrary):
static = isinstance(d, build.StaticLibrary)
libname = os.path.basename(lib) if verbatim else d.name
_link_library(libname, static, bundle=link_whole)
elif link_whole:
link_whole_args = rustc.linker.get_link_whole_for([lib])
args += [f'-Clink-arg={a}' for a in link_whole_args]
else:
lib = d.name

if lib:
_type = 'static' if isinstance(d, build.StaticLibrary) else 'dylib'
if modifiers:
_type += ':' + ','.join(modifiers)
args += ['-l', f'{_type}={lib}']
args.append(f'-Clink-arg={lib}')

for e in external_deps:
for a in e.get_link_args():
if a in rustc.native_static_libs:
# Exclude link args that rustc already add by default
continue
if a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')):
dir_, lib = os.path.split(a)
linkdirs.add(dir_)
lib, ext = os.path.splitext(lib)
if lib.startswith('lib'):
lib = lib[3:]
_type = 'static' if a.endswith(('.a', '.lib')) else 'dylib'
args.extend(['-l', f'{_type}={lib}'])
pass
elif a.startswith('-L'):
args.append(a)
elif a.startswith('-l'):
_type = 'static' if e.static else 'dylib'
args.extend(['-l', f'{_type}={a[2:]}'])
elif a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')) and isinstance(target, build.StaticLibrary):
dir_, lib = os.path.split(a)
linkdirs.add(dir_)
if not verbatim:
lib, ext = os.path.splitext(lib)
if lib.startswith('lib'):
lib = lib[3:]
static = a.endswith(('.a', '.lib'))
_link_library(lib, static)
else:
args.append(f'-Clink-arg={a}')

for d in linkdirs:
if d == '':
d = '.'
args += ['-L', d]
d = d or '.'
args.append(f'-L{d}')

# Because of the way rustc links, this must come after any potential
# library need to link with their stdlibs (C++ and Fortran, for example)
args.extend(target.get_used_stdlib_args('rust'))
args.extend(f'-Clink-arg={a}' for a in target.get_used_stdlib_args('rust'))

has_shared_deps = any(isinstance(dep, build.SharedLibrary) for dep in target_deps)
has_rust_shared_deps = any(dep.uses_rust()
Expand Down
70 changes: 46 additions & 24 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,21 +1276,21 @@ def get_extra_args(self, language: str) -> T.List[str]:
return self.extra_args[language]

@lru_cache(maxsize=None)
def get_dependencies(self) -> OrderedSet[Target]:
def get_dependencies(self) -> OrderedSet[BuildTargetTypes]:
# Get all targets needed for linking. This includes all link_with and
# link_whole targets, and also all dependencies of static libraries
# recursively. The algorithm here is closely related to what we do in
# get_internal_static_libraries(): Installed static libraries include
# objects from all their dependencies already.
result: OrderedSet[Target] = OrderedSet()
result: OrderedSet[BuildTargetTypes] = OrderedSet()
for t in itertools.chain(self.link_targets, self.link_whole_targets):
if t not in result:
result.add(t)
if isinstance(t, StaticLibrary):
t.get_dependencies_recurse(result)
return result

def get_dependencies_recurse(self, result: OrderedSet[Target], include_internals: bool = True) -> None:
def get_dependencies_recurse(self, result: OrderedSet[BuildTargetTypes], include_internals: bool = True) -> None:
# self is always a static library because we don't need to pull dependencies
# of shared libraries. If self is installed (not internal) it already
# include objects extracted from all its internal dependencies so we can
Expand All @@ -1299,7 +1299,7 @@ def get_dependencies_recurse(self, result: OrderedSet[Target], include_internals
for t in self.link_targets:
if t in result:
continue
if isinstance(t, SharedLibrary) and t.rust_crate_type == 'proc-macro':
if t.rust_crate_type == 'proc-macro':
continue
if include_internals or not t.is_internal():
result.add(t)
Expand Down Expand Up @@ -1394,7 +1394,7 @@ def get_external_deps(self) -> T.List[dependencies.Dependency]:
def is_internal(self) -> bool:
return False

def link(self, targets):
def link(self, targets: T.List[BuildTargetTypes]) -> None:
for t in targets:
if not isinstance(t, (Target, CustomTargetIndex)):
if isinstance(t, dependencies.ExternalLibrary):
Expand All @@ -1420,7 +1420,7 @@ def link(self, targets):
self.check_can_link_together(t)
self.link_targets.append(t)

def link_whole(self, targets, promoted: bool = False):
def link_whole(self, targets: T.List[BuildTargetTypes], promoted: bool = False) -> None:
for t in targets:
if isinstance(t, (CustomTarget, CustomTargetIndex)):
if not t.is_linkable_target():
Expand All @@ -1434,36 +1434,37 @@ def link_whole(self, targets, promoted: bool = False):
msg += "Use the 'pic' option to static_library to build with PIC."
raise InvalidArguments(msg)
self.check_can_link_together(t)
if isinstance(self, StaticLibrary) and not self.uses_rust():
if isinstance(self, StaticLibrary):
# When we're a static library and we link_whole: to another static
# library, we need to add that target's objects to ourselves.
self.check_can_extract_objects(t, origin=self, promoted=promoted)
self.objects += [t.extract_all_objects()]
self._bundle_static_library(t, promoted)
# If we install this static library we also need to include objects
# from all uninstalled static libraries it depends on.
if self.install:
for lib in t.get_internal_static_libraries(origin=self):
self.objects += [lib.extract_all_objects()]
for lib in t.get_internal_static_libraries():
self._bundle_static_library(lib, True)
self.link_whole_targets.append(t)

@lru_cache(maxsize=None)
def get_internal_static_libraries(self, origin: StaticLibrary) -> OrderedSet[Target]:
result: OrderedSet[Target] = OrderedSet()
self.get_internal_static_libraries_recurse(result, origin)
def get_internal_static_libraries(self) -> OrderedSet[BuildTargetTypes]:
result: OrderedSet[BuildTargetTypes] = OrderedSet()
self.get_internal_static_libraries_recurse(result)
return result

def get_internal_static_libraries_recurse(self, result: OrderedSet[Target], origin: StaticLibrary) -> None:
def get_internal_static_libraries_recurse(self, result: OrderedSet[BuildTargetTypes]) -> None:
for t in self.link_targets:
if t.is_internal() and t not in result:
self.check_can_extract_objects(t, origin, promoted=True)
result.add(t)
t.get_internal_static_libraries_recurse(result, origin)
t.get_internal_static_libraries_recurse(result)
for t in self.link_whole_targets:
if t.is_internal():
t.get_internal_static_libraries_recurse(result, origin)
t.get_internal_static_libraries_recurse(result)

def check_can_extract_objects(self, t: T.Union[Target, CustomTargetIndex], origin: StaticLibrary, promoted: bool = False) -> None:
if isinstance(t, (CustomTarget, CustomTargetIndex)) or t.uses_rust():
def _bundle_static_library(self, t: T.Union[BuildTargetTypes], promoted: bool = False) -> None:
if self.uses_rust():
# Rustc can bundle static libraries, no need to extract objects.
self.link_whole_targets.append(t)
elif isinstance(t, (CustomTarget, CustomTargetIndex)) or t.uses_rust():
# To extract objects from a custom target we would have to extract
# the archive, WIP implementation can be found in
# https://github.com/mesonbuild/meson/pull/9218.
Expand All @@ -1472,12 +1473,14 @@ def check_can_extract_objects(self, t: T.Union[Target, CustomTargetIndex], origi
# https://github.com/mesonbuild/meson/issues/10722
# https://github.com/mesonbuild/meson/issues/10723
# https://github.com/mesonbuild/meson/issues/10724
m = (f'Cannot link_whole a custom or Rust target {t.name!r} into a static library {origin.name!r}. '
m = (f'Cannot link_whole a custom or Rust target {t.name!r} into a static library {self.name!r}. '
'Instead, pass individual object files with the "objects:" keyword argument if possible.')
if promoted:
m += (f' Meson had to promote link to link_whole because {origin.name!r} is installed but not {t.name!r},'
m += (f' Meson had to promote link to link_whole because {self.name!r} is installed but not {t.name!r},'
f' and thus has to include objects from {t.name!r} to be usable.')
raise InvalidArguments(m)
else:
self.objects.append(t.extract_all_objects())

def check_can_link_together(self, t: BuildTargetTypes) -> None:
links_with_rust_abi = isinstance(t, BuildTarget) and t.uses_rust_abi()
Expand Down Expand Up @@ -2524,7 +2527,26 @@ def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalPr
raise InvalidArguments(f'Argument {c!r} in "command" is invalid')
return final_cmd

class CustomTarget(Target, CommandBase):
class CustomTargetBase:
''' Base class for CustomTarget and CustomTargetIndex

This base class can be used to provide a dummy implementation of some
private methods to avoid repeating `isinstance(t, BuildTarget)` when dealing
with custom targets.
'''

rust_crate_type = ''

def get_dependencies_recurse(self, result: OrderedSet[BuildTargetTypes], include_internals: bool = True) -> None:
pass

def get_internal_static_libraries(self) -> OrderedSet[BuildTargetTypes]:
return OrderedSet()

def get_internal_static_libraries_recurse(self, result: OrderedSet[BuildTargetTypes]) -> None:
pass

class CustomTarget(Target, CustomTargetBase, CommandBase):

typename = 'custom'

Expand Down Expand Up @@ -2901,7 +2923,7 @@ def get_default_install_dir(self) -> T.Tuple[str, str]:
return self.environment.get_jar_dir(), '{jardir}'

@dataclass(eq=False)
class CustomTargetIndex(HoldableObject):
class CustomTargetIndex(CustomTargetBase, HoldableObject):

"""A special opaque object returned by indexing a CustomTarget. This object
exists in Meson, but acts as a proxy in the backends, making targets depend
Expand Down
8 changes: 7 additions & 1 deletion test cases/rust/15 polyglot sharedlib/adder.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ adder* adder_create(int number) {
return a;
}

// adder_add is implemented in the Rust file.
// adder_add_r is implemented in the Rust file.
int adder_add_r(adder *a, int number);

int adder_add(adder *a, int number)
{
return adder_add_r(a, number);
}

void adder_destroy(adder *a) {
free(a);
Expand Down
11 changes: 9 additions & 2 deletions test cases/rust/15 polyglot sharedlib/adder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ pub struct Adder {
pub number: i32
}

extern "C" {
pub fn zero() -> i32;
pub fn zero_static() -> i32;
}

#[no_mangle]
pub extern fn adder_add(a: &Adder, number: i32) -> i32 {
return a.number + number;
pub extern fn adder_add_r(a: &Adder, number: i32) -> i32 {
unsafe {
return a.number + number + zero() + zero_static();
}
}
23 changes: 9 additions & 14 deletions test cases/rust/15 polyglot sharedlib/meson.build
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
project('adder', 'c', 'rust', version: '1.0.0')

if build_machine.system() != 'linux'
error('MESON_SKIP_TEST, this test only works on Linux. Patches welcome.')
endif
subdir('zero')

thread_dep = dependency('threads')
dl_dep = meson.get_compiler('c').find_library('dl', required: false)
m_dep = meson.get_compiler('c').find_library('m', required: false)

rl = static_library('radder', 'adder.rs', rust_crate_type: 'staticlib')
rl = shared_library('radder', 'adder.rs',
rust_abi: 'c',
link_with: [zero_shared, zero_static])

l = shared_library('adder', 'adder.c',
c_args: '-DBUILDING_ADDER',
link_with: rl,
version: '1.0.0',
soversion: '1',
link_args: '-Wl,-u,adder_add', # Ensure that Rust code is not removed as unused.
dependencies: [thread_dep, dl_dep, m_dep])
c_args: '-DBUILDING_ADDER',
link_with: rl,
version: '1.0.0',
soversion: '1',
)
test('adder', executable('addertest', 'addertest.c', link_with: l))
6 changes: 6 additions & 0 deletions test cases/rust/15 polyglot sharedlib/zero/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# They both have the same name, this tests we use +verbatim to distinguish them
# using their filename. It also ensures we pass the importlib on Windows.
# Those libs are in a subdir as regression test:
# https://github.com/mesonbuild/meson/issues/12484
zero_shared = shared_library('zero', 'zero.c')
zero_static = static_library('zero', 'zero_static.c')
11 changes: 11 additions & 0 deletions test cases/rust/15 polyglot sharedlib/zero/zero.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#if defined _WIN32 || defined __CYGWIN__
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif

EXPORT int zero(void);

int zero(void) {
return 0;
}
6 changes: 6 additions & 0 deletions test cases/rust/15 polyglot sharedlib/zero/zero_static.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
int zero_static(void);

int zero_static(void)
{
return 0;
}
4 changes: 4 additions & 0 deletions test cases/rust/20 transitive dependencies/diamond/func.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
int c_func(void);
int c_func(void) {
return 123;
}
Loading