From c1fa4c5941c76c7063c4446eded8bb1813fec2ec Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 29 Oct 2024 08:46:08 -0400 Subject: [PATCH] Fix regression when using both libraries 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. --- mesonbuild/build.py | 64 ++++++++----------- mesonbuild/interpreter/interpreter.py | 28 ++++---- mesonbuild/interpreter/interpreterobjects.py | 2 - mesonbuild/modules/pkgconfig.py | 2 +- .../frameworks/38 gir both_libraries/bar.c | 7 ++ .../frameworks/38 gir both_libraries/bar.h | 1 + .../frameworks/38 gir both_libraries/foo.c | 6 ++ .../frameworks/38 gir both_libraries/foo.h | 1 + .../38 gir both_libraries/meson.build | 37 +++++++++++ 9 files changed, 92 insertions(+), 56 deletions(-) create mode 100644 test cases/frameworks/38 gir both_libraries/bar.c create mode 100644 test cases/frameworks/38 gir both_libraries/bar.h create mode 100644 test cases/frameworks/38 gir both_libraries/foo.c create mode 100644 test cases/frameworks/38 gir both_libraries/foo.h create mode 100644 test cases/frameworks/38 gir both_libraries/meson.build diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 460ed549be92..d500b72bfacb 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -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) @@ -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 @@ -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) @@ -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 @@ -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) @@ -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): @@ -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 @@ -2510,13 +2512,6 @@ def __init__(self, shared: SharedLibrary, static: StaticLibrary, preferred_libra def __repr__(self) -> str: return f'' - 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 @@ -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 @@ -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 diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 7bb2337425c5..ee3e07831085 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -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 @@ -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'] @@ -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: @@ -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: @@ -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) @@ -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 @@ -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) @@ -3508,7 +3509,6 @@ def is_subproject(self) -> bool: @typed_pos_args('set_variable', str, object) @noKwargs @noArgsFlattening - @noSecondLevelHolderResolving def func_set_variable(self, node: mparser.BaseNode, args: T.Tuple[str, object], kwargs: 'TYPE_kwargs') -> None: varname, value = args self.set_variable(varname, value, holderify=True) diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index a919102607be..88e12f5f8e8c 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -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, diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 1bdf82931a94..c8cffdda7aa0 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -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 diff --git a/test cases/frameworks/38 gir both_libraries/bar.c b/test cases/frameworks/38 gir both_libraries/bar.c new file mode 100644 index 000000000000..4cb41f798294 --- /dev/null +++ b/test cases/frameworks/38 gir both_libraries/bar.c @@ -0,0 +1,7 @@ +#include "bar.h" +#include "foo.h" + +int bar_func(void) +{ + return foo_func() + 42; +} diff --git a/test cases/frameworks/38 gir both_libraries/bar.h b/test cases/frameworks/38 gir both_libraries/bar.h new file mode 100644 index 000000000000..d22827b837f7 --- /dev/null +++ b/test cases/frameworks/38 gir both_libraries/bar.h @@ -0,0 +1 @@ +int bar_func(void); diff --git a/test cases/frameworks/38 gir both_libraries/foo.c b/test cases/frameworks/38 gir both_libraries/foo.c new file mode 100644 index 000000000000..b88aa91dabb4 --- /dev/null +++ b/test cases/frameworks/38 gir both_libraries/foo.c @@ -0,0 +1,6 @@ +#include "foo.h" + +int foo_func(void) +{ + return 42; +} diff --git a/test cases/frameworks/38 gir both_libraries/foo.h b/test cases/frameworks/38 gir both_libraries/foo.h new file mode 100644 index 000000000000..2a0867249307 --- /dev/null +++ b/test cases/frameworks/38 gir both_libraries/foo.h @@ -0,0 +1 @@ +int foo_func(void); diff --git a/test cases/frameworks/38 gir both_libraries/meson.build b/test cases/frameworks/38 gir both_libraries/meson.build new file mode 100644 index 000000000000..e9e45b22364c --- /dev/null +++ b/test cases/frameworks/38 gir both_libraries/meson.build @@ -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, +)