Skip to content

Commit

Permalink
Fix regression when using both libraries
Browse files Browse the repository at this point in the history
This partly reverts #12632 and provide an alternative implementation.
The issue is we cannot propagate BothLibraries objects beyond the
interpreter because it breaks many isinstance(obj, SharedLibrary) cases.

Instead make SharedLibrary and StaticLibrary cross reference each other
so we can take their brother library in as_static() and as_shared()
methods.
  • Loading branch information
xclaesse committed Nov 1, 2024
1 parent 4ac3e7d commit 36cd1ff
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 55 deletions.
64 changes: 25 additions & 39 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,8 @@ def __init__(
# we have to call process_compilers() first and we need to process libraries
# from link_with and link_whole first.
# See https://github.com/mesonbuild/meson/pull/11957#issuecomment-1629243208.
link_targets = self.extract_targets_as_list(kwargs, 'link_with')
link_whole_targets = self.extract_targets_as_list(kwargs, 'link_whole')
link_targets = extract_as_list(kwargs, 'link_with') + self.link_targets
link_whole_targets = extract_as_list(kwargs, 'link_whole') + self.link_whole_targets
self.link_targets.clear()
self.link_whole_targets.clear()
self.link(link_targets)
Expand Down Expand Up @@ -1732,21 +1732,7 @@ def process_vs_module_defs_kw(self, kwargs: T.Dict[str, T.Any]) -> None:
'a file object, a Custom Target, or a Custom Target Index')
self.process_link_depends(path)

def extract_targets_as_list(self, kwargs: T.Dict[str, T.Union[LibTypes, T.Sequence[LibTypes]]], key: T.Literal['link_with', 'link_whole']) -> T.List[LibTypes]:
bl_type = self.environment.coredata.get_option(OptionKey('default_both_libraries'))
if bl_type == 'auto':
bl_type = 'static' if isinstance(self, StaticLibrary) else 'shared'

def _resolve_both_libs(lib: LibTypes) -> LibTypes:
if isinstance(lib, BothLibraries):
return lib.get(bl_type)
return lib

self_libs: T.List[LibTypes] = self.link_targets if key == 'link_with' else self.link_whole_targets
lib_list = listify(kwargs.get(key, [])) + self_libs
return [_resolve_both_libs(t) for t in lib_list]

def get(self, lib_type: T.Literal['static', 'shared', 'auto']) -> LibTypes:
def get(self, lib_type: T.Literal['static', 'shared']) -> LibTypes:
"""Base case used by BothLibraries"""
return self

Expand Down Expand Up @@ -2117,6 +2103,7 @@ def __init__(
compilers: T.Dict[str, 'Compiler'],
kwargs):
self.prelink = T.cast('bool', kwargs.get('prelink', False))
self.shared_library: T.Optional[SharedLibrary] = None
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects,
environment, compilers, kwargs)

Expand Down Expand Up @@ -2199,6 +2186,14 @@ def is_linkable_target(self):
def is_internal(self) -> bool:
return not self.install

def set_shared(self, shared_library: SharedLibrary) -> None:
self.shared_library = shared_library

def get(self, lib_type: T.Literal['static', 'shared']) -> LibTypes:
if self.shared_library and lib_type == 'shared':
return self.shared_library
return self

class SharedLibrary(BuildTarget):
known_kwargs = known_shlib_kwargs

Expand Down Expand Up @@ -2228,8 +2223,7 @@ def __init__(
self.import_filename = None
# The debugging information file this target will generate
self.debug_filename = None
# Use by the pkgconfig module
self.shared_library_only = False
self.static_library: T.Optional[StaticLibrary] = None
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects,
environment, compilers, kwargs)

Expand Down Expand Up @@ -2465,6 +2459,14 @@ def type_suffix(self):
def is_linkable_target(self):
return True

def set_static(self, static_library: StaticLibrary) -> None:
self.static_library = static_library

def get(self, lib_type: T.Literal['static', 'shared']) -> LibTypes:
if self.static_library and lib_type == 'static':
return self.static_library
return self

# A shared library that is meant to be used with dlopen rather than linking
# into something else.
class SharedModule(SharedLibrary):
Expand Down Expand Up @@ -2501,7 +2503,7 @@ def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, No
return self.environment.get_shared_module_dir(), '{moduledir_shared}'

class BothLibraries(SecondLevelHolder):
def __init__(self, shared: SharedLibrary, static: StaticLibrary, preferred_library: Literal['shared', 'static', 'auto']) -> None:
def __init__(self, shared: SharedLibrary, static: StaticLibrary, preferred_library: Literal['shared', 'static']) -> None:
self._preferred_library = preferred_library
self.shared = shared
self.static = static
Expand All @@ -2510,13 +2512,6 @@ def __init__(self, shared: SharedLibrary, static: StaticLibrary, preferred_libra
def __repr__(self) -> str:
return f'<BothLibraries: static={repr(self.static)}; shared={repr(self.shared)}>'

def get(self, lib_type: T.Literal['static', 'shared', 'auto']) -> LibTypes:
if lib_type == 'static':
return self.static
if lib_type == 'shared':
return self.shared
return self.get_default_object()

def get_default_object(self) -> T.Union[StaticLibrary, SharedLibrary]:
if self._preferred_library == 'shared':
return self.shared
Expand Down Expand Up @@ -2584,7 +2579,7 @@ def get_internal_static_libraries(self) -> OrderedSet[BuildTargetTypes]:
def get_internal_static_libraries_recurse(self, result: OrderedSet[BuildTargetTypes]) -> None:
pass

def get(self, lib_type: T.Literal['static', 'shared', 'auto']) -> LibTypes:
def get(self, lib_type: T.Literal['static', 'shared']) -> LibTypes:
"""Base case used by BothLibraries"""
return self

Expand Down Expand Up @@ -2909,23 +2904,14 @@ class AliasTarget(RunTarget):

typename = 'alias'

def __init__(self, name: str, dependencies: T.Sequence[T.Union[Target, BothLibraries]],
def __init__(self, name: str, dependencies: T.Sequence['Target'],
subdir: str, subproject: str, environment: environment.Environment):
super().__init__(name, [], list(self._deps_generator(dependencies)), subdir, subproject, environment)
super().__init__(name, [], dependencies, subdir, subproject, environment)

def __repr__(self):
repr_str = "<{0} {1}>"
return repr_str.format(self.__class__.__name__, self.get_id())

@staticmethod
def _deps_generator(dependencies: T.Sequence[T.Union[Target, BothLibraries]]) -> T.Iterator[Target]:
for dep in dependencies:
if isinstance(dep, BothLibraries):
yield dep.shared
yield dep.static
else:
yield dep

class Jar(BuildTarget):
known_kwargs = known_jar_kwargs

Expand Down
27 changes: 14 additions & 13 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureBroken, FeatureNewKwargs
from ..interpreterbase import ObjectHolder, ContextManagerObject
from ..interpreterbase import stringifyUserArguments, resolve_second_level_holders
from ..interpreterbase import stringifyUserArguments
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
from ..optinterpreter import optname_regex

Expand Down Expand Up @@ -690,7 +690,6 @@ def func_files(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwa
KwargInfo('version', (str, NoneType)),
KwargInfo('objects', ContainerTypeInfo(list, build.ExtractedObjects), listify=True, default=[], since='1.1.0'),
)
@noSecondLevelHolderResolving
def func_declare_dependency(self, node: mparser.BaseNode, args: T.List[TYPE_var],
kwargs: kwtypes.FuncDeclareDependency) -> dependencies.Dependency:
deps = kwargs['dependencies']
Expand Down Expand Up @@ -1873,14 +1872,11 @@ def func_static_lib(self, node: mparser.BaseNode,
def func_shared_lib(self, node: mparser.BaseNode,
args: T.Tuple[str, SourcesVarargsType],
kwargs: kwtypes.SharedLibrary) -> build.SharedLibrary:
holder = self.build_target(node, args, kwargs, build.SharedLibrary)
holder.shared_library_only = True
return holder
return self.build_target(node, args, kwargs, build.SharedLibrary)

@permittedKwargs(known_library_kwargs)
@typed_pos_args('both_libraries', str, varargs=SOURCES_VARARGS)
@typed_kwargs('both_libraries', *LIBRARY_KWS, allow_unknown=True)
@noSecondLevelHolderResolving
def func_both_lib(self, node: mparser.BaseNode,
args: T.Tuple[str, SourcesVarargsType],
kwargs: kwtypes.Library) -> build.BothLibraries:
Expand All @@ -1898,7 +1894,6 @@ def func_shared_module(self, node: mparser.BaseNode,
@permittedKwargs(known_library_kwargs)
@typed_pos_args('library', str, varargs=SOURCES_VARARGS)
@typed_kwargs('library', *LIBRARY_KWS, allow_unknown=True)
@noSecondLevelHolderResolving
def func_library(self, node: mparser.BaseNode,
args: T.Tuple[str, SourcesVarargsType],
kwargs: kwtypes.Library) -> build.Executable:
Expand All @@ -1916,15 +1911,12 @@ def func_jar(self, node: mparser.BaseNode,
@permittedKwargs(known_build_target_kwargs)
@typed_pos_args('build_target', str, varargs=SOURCES_VARARGS)
@typed_kwargs('build_target', *BUILD_TARGET_KWS, allow_unknown=True)
@noSecondLevelHolderResolving
def func_build_target(self, node: mparser.BaseNode,
args: T.Tuple[str, SourcesVarargsType],
kwargs: kwtypes.BuildTarget
) -> T.Union[build.Executable, build.StaticLibrary, build.SharedLibrary,
build.SharedModule, build.BothLibraries, build.Jar]:
target_type = kwargs['target_type']
if target_type not in {'both_libraries', 'library'}:
args, kwargs = resolve_second_level_holders(args, kwargs)

if target_type == 'executable':
return self.build_target(node, args, kwargs, build.Executable)
Expand Down Expand Up @@ -2177,7 +2169,14 @@ def func_alias_target(self, node: mparser.BaseNode, args: T.Tuple[str, T.List[T.
name, deps = args
if any(isinstance(d, build.RunTarget) for d in deps):
FeatureNew.single_use('alias_target that depends on run_targets', '0.60.0', self.subproject)
tg = build.AliasTarget(name, deps, self.subdir, self.subproject, self.environment)
real_deps: T.List[build.Target] = []
for d in deps:
if isinstance(d, build.BothLibraries):
real_deps.append(d.shared)
real_deps.append(d.static)
else:
real_deps.append(d)
tg = build.AliasTarget(name, real_deps, self.subdir, self.subproject, self.environment)
self.add_target(name, tg)
return tg

Expand Down Expand Up @@ -3283,16 +3282,18 @@ def build_both_libraries(self, node: mparser.BaseNode, args: T.Tuple[str, Source
# Keep only compilers used for linking
static_lib.compilers = {k: v for k, v in static_lib.compilers.items() if k in compilers.clink_langs}

# Cross reference them to implement as_shared() and as_static() methods.
shared_lib.set_static(static_lib)
static_lib.set_shared(shared_lib)

return build.BothLibraries(shared_lib, static_lib, preferred_library)

def build_library(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], kwargs: kwtypes.Library):
default_library = self.coredata.get_option(OptionKey('default_library', subproject=self.subproject))
assert isinstance(default_library, str), 'for mypy'
if default_library == 'shared':
args, kwargs = resolve_second_level_holders(args, kwargs)
return self.build_target(node, args, T.cast('kwtypes.StaticLibrary', kwargs), build.SharedLibrary)
elif default_library == 'static':
args, kwargs = resolve_second_level_holders(args, kwargs)
return self.build_target(node, args, T.cast('kwtypes.SharedLibrary', kwargs), build.StaticLibrary)
elif default_library == 'both':
return self.build_both_libraries(node, args, kwargs)
Expand Down
2 changes: 0 additions & 2 deletions mesonbuild/interpreter/interpreterobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,8 +1001,6 @@ class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]):

class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]):
def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'):
# FIXME: This build target always represents the shared library, but
# that should be configurable.
super().__init__(libs, interp)
self.methods.update({'get_shared_lib': self.get_shared_lib_method,
'get_static_lib': self.get_static_lib_method,
Expand Down
2 changes: 1 addition & 1 deletion mesonbuild/modules/pkgconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def _process_libs(
if obj.found():
processed_libs += obj.get_link_args()
processed_cflags += obj.get_compile_args()
elif isinstance(obj, build.SharedLibrary) and obj.shared_library_only:
elif isinstance(obj, build.SharedLibrary) and obj.static_library is None:
# Do not pull dependencies for shared libraries because they are
# only required for static linking. Adding private requires has
# the side effect of exposing their cflags, which is the
Expand Down
7 changes: 7 additions & 0 deletions test cases/frameworks/38 gir both_libraries/bar.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "bar.h"
#include "foo.h"

int bar_func(void)
{
return foo_func() + 42;
}
1 change: 1 addition & 0 deletions test cases/frameworks/38 gir both_libraries/bar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int bar_func(void);
6 changes: 6 additions & 0 deletions test cases/frameworks/38 gir both_libraries/foo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "foo.h"

int foo_func(void)
{
return 42;
}
1 change: 1 addition & 0 deletions test cases/frameworks/38 gir both_libraries/foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int foo_func(void);
37 changes: 37 additions & 0 deletions test cases/frameworks/38 gir both_libraries/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
project('gir both libraries', 'c')

gir = dependency('gobject-introspection-1.0', required: false)
if not gir.found()
error('MESON_SKIP_TEST gobject-introspection not found.')
endif

gnome = import('gnome')

# Regression test simulating how GStreamer generate its GIRs.
# Generated gobject-introspection binaries for every GStreamer libraries must
# first call gst_init() defined in the main libgstreamer, which means they need
# to link on that lib.
# A regression caused by https://github.com/mesonbuild/meson/pull/12632 made
# Meson not link the binary generated for bar with libfoo in the case it uses
# both_libraries().

libfoo = both_libraries('foo', 'foo.c')
foo_gir = gnome.generate_gir(libfoo,
namespace: 'foo',
nsversion: '1.0',
sources: ['foo.c', 'foo.h'],
)
foo_dep = declare_dependency(
link_with: libfoo,
#link_with: libfoo.get_shared_lib(),
sources: foo_gir,
)

libbar = both_libraries('bar', 'bar.c', dependencies: foo_dep)
gnome.generate_gir(libbar,
namespace: 'bar',
nsversion: '1.0',
sources: ['bar.c', 'bar.h'],
extra_args: '--add-init-section=extern void foo_func(void);foo_func();',
dependencies: foo_dep,
)

0 comments on commit 36cd1ff

Please sign in to comment.