Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the use of both_libraries #12632

Merged
merged 4 commits into from
Sep 6, 2024
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
14 changes: 14 additions & 0 deletions docs/markdown/Builtin-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ machine](#specifying-options-per-machine) section for details.
| genvslite {vs2022} | vs2022 | Setup multi-builtype ninja build directories and Visual Studio solution | no | no |
| buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | no |
| debug | true | Enable debug symbols and other information | no | no |
| default_both_libraries {shared, static, auto} | shared | Default library type for both_libraries | no | no |
| default_library {shared, static, both} | shared | Default library type | no | yes |
| errorlogs | true | Whether to print the logs from failing tests. | no | no |
| install_umask {preserve, 0000-0777} | 022 | Default umask to apply on permissions of installed files | no | no |
Expand Down Expand Up @@ -177,6 +178,19 @@ fails.

`vsenv` is `true` by default when using the `vs` backend.


#### Details for `default_both_libraries`

Since `1.6.0`, you can select the default type of library selected when using
a `both_libraries` object. This can be either 'shared' (default value, compatible
with previous meson versions), 'static', or 'auto'. With auto, the value from
`default_library` option is used, unless it is 'both', in which case 'shared'
is used instead.

When `default_both_libraries` is 'auto', passing a [[@both_libs]] dependecy
in [[both_libraries]] will link the static dependency with the static lib,
and the shared dependency with the shared lib.

## Base options

These are set in the same way as universal options, either by
Expand Down
5 changes: 5 additions & 0 deletions docs/markdown/snippets/alias_target_of_both_libraries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## `alias_target` of `both_libraries`

Previously, when passing a [[@both_libs]] object to [[alias_target]], the alias
would only point to the shared library. It now points to both the static and the
shared library.
11 changes: 11 additions & 0 deletions docs/markdown/snippets/default_both_libraries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## New built-in option for default both_libraries

`both_libraries` targets used to be considered as a shared library by default.
There is now the `default_both_libraries` option to change this default.

When `default_both_libraries` is 'auto', [[both_libraries]] with dependencies
that are [[@both_libs]] themselves will link with the same kind of library.
For example, if `libA` is a [[@both_libs]] and `libB` is a [[@both_libs]]
linked with `libA` (or with an internal dependency on `libA`),
the static lib of `libB` will link with the static lib of `libA`, and the
shared lib of `libA` will link with the shared lib of `libB`.
8 changes: 8 additions & 0 deletions docs/markdown/snippets/dep_as_shared_as_static.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## New `as_static` and `as_shared` methods on internal dependencies

[[@dep]] object returned by [[declare_dependency]] now has `.as_static()` and
`.as_shared()` methods, to convert to a dependency that prefers the `static`
or the `shared` version of the linked [[@both_libs]] target.

When the same dependency is used without those methods, the
`default_both_libraries` option determines which version is used.
3 changes: 3 additions & 0 deletions docs/yaml/functions/alias_target.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ description: |
are built. Dependencies can be any build target. Since 0.60.0, this includes
[[@run_tgt]].

*Since 1.6.0* passing a [[@both_libs]] object builds both shared and
static libraries.

posargs:
target_name:
type: str
Expand Down
25 changes: 25 additions & 0 deletions docs/yaml/objects/dep.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,28 @@ methods:
pkgconfig_define:
type: list[str]
description: See [[dep.get_pkgconfig_variable]]

- name: as_static
returns: dep
since: 1.6.0
description: |
Only for dependencies created with [[declare_dependency]],
returns a copy of the dependency object that prefer the `static` version
of [[both_libraries]].
kwargs:
recursive:
type: bool
description: If true, this is recursively applied to dependencies

- name: as_shared
returns: dep
since: 1.6.0
description: |
Only for dependencies created with [[declare_dependency]],
returns a copy of the dependency object that prefer the `shared` version
of [[both_libraries]].
kwargs:
recursive:
type: bool
description: If true, this is recursively applied to dependencies

56 changes: 49 additions & 7 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def get_target_macos_dylib_install_name(ld) -> str:
name.append('.dylib')
return ''.join(name)


class InvalidArguments(MesonException):
pass

Expand Down Expand Up @@ -782,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 = extract_as_list(kwargs, 'link_with') + self.link_targets
link_whole_targets = extract_as_list(kwargs, 'link_whole') + self.link_whole_targets
link_targets = self.extract_targets_as_list(kwargs, 'link_with')
link_whole_targets = self.extract_targets_as_list(kwargs, 'link_whole')
self.link_targets.clear()
self.link_whole_targets.clear()
self.link(link_targets)
Expand Down Expand Up @@ -1731,6 +1732,24 @@ 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:
"""Base case used by BothLibraries"""
return self

class FileInTargetPrivateDir:
"""Represents a file with the path '/path/to/build/target_private_dir/fname'.
target_private_dir is the return value of get_target_private_dir which is e.g. 'subdir/target.p'.
Expand Down Expand Up @@ -2477,22 +2496,32 @@ 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) -> None:
self._preferred_library = 'shared'
def __init__(self, shared: SharedLibrary, static: StaticLibrary, preferred_library: Literal['shared', 'static', 'auto']) -> None:
self._preferred_library = preferred_library
self.shared = shared
self.static = static
self.subproject = self.shared.subproject

def __repr__(self) -> str:
return f'<BothLibraries: static={repr(self.static)}; shared={repr(self.shared)}>'

def get_default_object(self) -> BuildTarget:
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
elif self._preferred_library == 'static':
return self.static
raise MesonBugException(f'self._preferred_library == "{self._preferred_library}" is neither "shared" nor "static".')

def get_id(self) -> str:
return self.get_default_object().get_id()

class CommandBase:

depend_files: T.List[File]
Expand Down Expand Up @@ -2550,6 +2579,10 @@ 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:
"""Base case used by BothLibraries"""
return self

class CustomTarget(Target, CustomTargetBase, CommandBase):

typename = 'custom'
Expand Down Expand Up @@ -2871,14 +2904,23 @@ class AliasTarget(RunTarget):

typename = 'alias'

def __init__(self, name: str, dependencies: T.Sequence['Target'],
def __init__(self, name: str, dependencies: T.Sequence[T.Union[Target, BothLibraries]],
subdir: str, subproject: str, environment: environment.Environment):
super().__init__(name, [], dependencies, subdir, subproject, environment)
super().__init__(name, [], list(self._deps_generator(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
22 changes: 22 additions & 0 deletions mesonbuild/dependencies/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ def generate_system_dependency(self, include_type: str) -> 'Dependency':
new_dep.include_type = self._process_include_type_kw({'include_type': include_type})
return new_dep

def get_as_static(self, recursive: bool) -> Dependency:
"""Used as base case for internal_dependency"""
return self

def get_as_shared(self, recursive: bool) -> Dependency:
"""Used as base case for internal_dependency"""
return self

class InternalDependency(Dependency):
def __init__(self, version: str, incdirs: T.List['IncludeDirs'], compile_args: T.List[str],
link_args: T.List[str],
Expand Down Expand Up @@ -345,6 +353,20 @@ def generate_link_whole_dependency(self) -> Dependency:
new_dep.libraries = []
return new_dep

def get_as_static(self, recursive: bool) -> Dependency:
new_dep = copy.copy(self)
new_dep.libraries = [lib.get('static') for lib in self.libraries]
if recursive:
new_dep.ext_deps = [dep.get_as_static(True) for dep in self.ext_deps]
return new_dep

def get_as_shared(self, recursive: bool) -> Dependency:
new_dep = copy.copy(self)
new_dep.libraries = [lib.get('shared') for lib in self.libraries]
if recursive:
new_dep.ext_deps = [dep.get_as_shared(True) for dep in self.ext_deps]
return new_dep

class HasNativeKwarg:
def __init__(self, kwargs: T.Dict[str, T.Any]):
self.for_machine = self.get_for_machine_from_kwargs(kwargs)
Expand Down
25 changes: 20 additions & 5 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
from ..interpreterbase import stringifyUserArguments, resolve_second_level_holders
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
from ..optinterpreter import optname_regex

Expand Down Expand Up @@ -690,6 +690,7 @@ 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 @@ -1875,6 +1876,7 @@ def func_shared_lib(self, node: mparser.BaseNode,
@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 @@ -1892,6 +1894,7 @@ 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 @@ -1909,12 +1912,16 @@ 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)
elif target_type == 'shared_library':
Expand Down Expand Up @@ -2158,10 +2165,11 @@ def func_run_target(self, node: mparser.FunctionNode, args: T.Tuple[str],
return tg

@FeatureNew('alias_target', '0.52.0')
@typed_pos_args('alias_target', str, varargs=build.Target, min_varargs=1)
@typed_pos_args('alias_target', str, varargs=(build.Target, build.BothLibraries), min_varargs=1)
@noKwargs
def func_alias_target(self, node: mparser.BaseNode, args: T.Tuple[str, T.List[build.Target]],
kwargs: 'TYPE_kwargs') -> build.AliasTarget:
@noSecondLevelHolderResolving
def func_alias_target(self, node: mparser.BaseNode, args: T.Tuple[str, T.List[T.Union[build.Target, build.BothLibraries]]],
kwargs: TYPE_kwargs) -> build.AliasTarget:
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)
Expand Down Expand Up @@ -3229,6 +3237,11 @@ def add_target(self, name: str, tobj: build.Target) -> None:
def build_both_libraries(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], kwargs: kwtypes.Library) -> build.BothLibraries:
shared_lib = self.build_target(node, args, kwargs, build.SharedLibrary)
static_lib = self.build_target(node, args, kwargs, build.StaticLibrary)
preferred_library = self.coredata.get_option(OptionKey('default_both_libraries'))
if preferred_library == 'auto':
preferred_library = self.coredata.get_option(OptionKey('default_library'))
if preferred_library == 'both':
preferred_library = 'shared'

if self.backend.name == 'xcode':
# Xcode is a bit special in that you can't (at least for the moment)
Expand Down Expand Up @@ -3260,14 +3273,16 @@ 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}

return build.BothLibraries(shared_lib, static_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
28 changes: 28 additions & 0 deletions mesonbuild/interpreter/interpreterobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class EnvironmentSeparatorKW(TypedDict):

separator: str

class InternalDependencyAsKW(TypedDict):

recursive: bool

_ERROR_MSG_KW: KwargInfo[T.Optional[str]] = KwargInfo('error_message', (str, NoneType))


Expand Down Expand Up @@ -462,6 +466,8 @@ def __init__(self, dep: Dependency, interpreter: 'Interpreter'):
'include_type': self.include_type_method,
'as_system': self.as_system_method,
'as_link_whole': self.as_link_whole_method,
'as_static': self.as_static_method,
'as_shared': self.as_shared_method,
})

def found(self) -> bool:
Expand Down Expand Up @@ -580,6 +586,28 @@ def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> D
new_dep = self.held_object.generate_link_whole_dependency()
return new_dep

@FeatureNew('dependency.as_static', '1.6.0')
@noPosargs
@typed_kwargs(
'dependency.as_static',
KwargInfo('recursive', bool, default=False),
)
def as_static_method(self, args: T.List[TYPE_var], kwargs: InternalDependencyAsKW) -> Dependency:
if not isinstance(self.held_object, InternalDependency):
raise InterpreterException('as_static method is only supported on declare_dependency() objects')
return self.held_object.get_as_static(kwargs['recursive'])

@FeatureNew('dependency.as_shared', '1.6.0')
@noPosargs
@typed_kwargs(
'dependency.as_shared',
KwargInfo('recursive', bool, default=False),
)
def as_shared_method(self, args: T.List[TYPE_var], kwargs: InternalDependencyAsKW) -> Dependency:
if not isinstance(self.held_object, InternalDependency):
raise InterpreterException('as_shared method is only supported on declare_dependency() objects')
return self.held_object.get_as_shared(kwargs['recursive'])

_EXTPROG = T.TypeVar('_EXTPROG', bound=ExternalProgram)

class _ExternalProgramHolder(ObjectHolder[_EXTPROG]):
Expand Down
Loading
Loading