refactor: Refactor BothLibraries logic

This commit introduces a new type of `HoldableObject`: The
`SecondLevelHolder`. The primary purpose of this class is
to handle cases where two (or more) `HoldableObject`s are
stored at the same time (with one default object). The
best (and currently only) example here is the `BothLibraries`
class.
pull/8935/head^2
Daniel Mensinger 3 years ago
parent b95d6e319f
commit 8f7343831b
  1. 16
      mesonbuild/build.py
  2. 11
      mesonbuild/dependencies/base.py
  3. 5
      mesonbuild/interpreter/interpreter.py
  4. 6
      mesonbuild/interpreter/interpreterobjects.py
  5. 5
      mesonbuild/interpreterbase/__init__.py
  6. 4
      mesonbuild/interpreterbase/baseobjects.py
  7. 4
      mesonbuild/interpreterbase/decorators.py
  8. 13
      mesonbuild/interpreterbase/helpers.py
  9. 4
      mesonbuild/interpreterbase/interpreterbase.py
  10. 9
      mesonbuild/mesonlib/universal.py
  11. 2
      mesonbuild/modules/gnome.py
  12. 19
      mesonbuild/modules/pkgconfig.py
  13. 8
      test cases/common/178 bothlibraries/meson.build

@ -28,7 +28,7 @@ from . import dependencies
from . import mlog
from . import programs
from .mesonlib import (
HoldableObject,
HoldableObject, SecondLevelHolder,
File, MesonException, MachineChoice, PerMachine, OrderedSet, listify,
extract_as_list, typeslistify, stringlistify, classify_unity_sources,
get_filenames_templates_dict, substitute_values, has_path_sep,
@ -1234,8 +1234,6 @@ You probably should put it in link_with instead.''')
def link(self, target):
for t in listify(target):
if isinstance(t, BothLibraries):
t = t.get_preferred_library()
if isinstance(self, StaticLibrary) and self.need_install:
if isinstance(t, (CustomTarget, CustomTargetIndex)):
if not t.should_install():
@ -1264,9 +1262,6 @@ You probably should put it in link_with instead.''')
def link_whole(self, target):
for t in listify(target):
# Always use the static library from BothLibraries, since shared libs aren't supported anyway
if isinstance(t, BothLibraries):
t = t.static
if isinstance(t, (CustomTarget, CustomTargetIndex)):
if not t.is_linkable_target():
raise InvalidArguments(f'Custom target {t!r} is not linkable.')
@ -2152,7 +2147,7 @@ class SharedModule(SharedLibrary):
def get_default_install_dir(self, environment):
return environment.get_shared_module_dir()
class BothLibraries(HoldableObject):
class BothLibraries(SecondLevelHolder):
def __init__(self, shared: SharedLibrary, static: StaticLibrary) -> None:
self._preferred_library = 'shared'
self.shared = shared
@ -2162,7 +2157,7 @@ class BothLibraries(HoldableObject):
def __repr__(self) -> str:
return f'<BothLibraries: static={repr(self.static)}; shared={repr(self.shared)}>'
def get_preferred_library(self) -> BuildTarget:
def get_default_object(self) -> BuildTarget:
if self._preferred_library == 'shared':
return self.shared
elif self._preferred_library == 'static':
@ -2174,8 +2169,6 @@ class CommandBase:
cmd = listify(cmd)
final_cmd = []
for c in cmd:
if isinstance(c, BothLibraries):
c = c.get_preferred_library()
if isinstance(c, str):
final_cmd.append(c)
elif isinstance(c, File):
@ -2274,7 +2267,6 @@ class CustomTarget(Target, CommandBase):
def process_kwargs(self, kwargs, backend):
self.process_kwargs_base(kwargs)
self.sources = extract_as_list(kwargs, 'input')
self.sources = [x.get_preferred_library() if isinstance(x, BothLibraries) else x for x in self.sources]
if 'output' not in kwargs:
raise InvalidArguments('Missing keyword argument "output".')
self.outputs = listify(kwargs['output'])
@ -2651,8 +2643,6 @@ def get_sources_string_names(sources, backend):
'''
names = []
for s in sources:
if isinstance(s, BothLibraries):
s = s.get_preferred_library()
if isinstance(s, str):
names.append(s)
elif isinstance(s, (BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList)):

@ -29,7 +29,7 @@ from ..interpreterbase import FeatureDeprecated
if T.TYPE_CHECKING:
from ..compilers.compilers import Compiler
from ..environment import Environment
from ..build import BuildTarget, BothLibraries
from ..build import BuildTarget
from ..mesonlib import FileOrString
@ -222,8 +222,8 @@ class Dependency(HoldableObject):
class InternalDependency(Dependency):
def __init__(self, version: str, incdirs: T.List[str], compile_args: T.List[str],
link_args: T.List[str], libraries: T.List[T.Union['BuildTarget', 'BothLibraries']],
whole_libraries: T.List[T.Union['BuildTarget', 'BothLibraries']], sources: T.List['FileOrString'],
link_args: T.List[str], libraries: T.List['BuildTarget'],
whole_libraries: T.List['BuildTarget'], sources: T.List['FileOrString'],
ext_deps: T.List[Dependency], variables: T.Dict[str, T.Any]):
super().__init__(DependencyTypeName('internal'), {})
self.version = version
@ -237,11 +237,6 @@ class InternalDependency(Dependency):
self.ext_deps = ext_deps
self.variables = variables
# Deal with BothLibraries
from ..build import BothLibraries
self.libraries = [x.get_preferred_library() if isinstance(x, BothLibraries) else x for x in self.libraries]
self.whole_libraries = [x.static if isinstance(x, BothLibraries) else x for x in self.whole_libraries]
def __deepcopy__(self, memo: T.Dict[int, 'InternalDependency']) -> 'InternalDependency':
result = self.__class__.__new__(self.__class__)
assert isinstance(result, InternalDependency)

@ -26,7 +26,7 @@ from ..programs import ExternalProgram, NonExistingExternalProgram
from ..dependencies import Dependency
from ..depfile import DepFile
from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args
from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening, unholder_return
from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, unholder_return
from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
@ -2537,8 +2537,6 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey
sources = [sources]
results: T.List['SourceOutputs'] = []
for s in sources:
if isinstance(s, build.BothLibraries):
s = s.get_preferred_library()
if isinstance(s, str):
self.validate_within_subproject(self.subdir, s)
results.append(mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s))
@ -2713,6 +2711,7 @@ This will become a hard error in the future.''', location=self.current_node)
@noKwargs
@noArgsFlattening
@noSecondLevelHolderResolving
def func_set_variable(self, node, args, kwargs):
if len(args) != 2:
raise InvalidCode('Set_variable takes two arguments.')

@ -19,7 +19,7 @@ from ..interpreterbase import (
FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated,
typed_pos_args, typed_kwargs, KwargInfo, stringArgs, permittedKwargs,
noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs,
flatten, InterpreterException, InvalidArguments, InvalidCode)
flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import ExternalProgram
from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe
@ -754,6 +754,8 @@ class ModuleObjectHolder(ObjectHolder[ModuleObject]):
raise InvalidCode(f'Unknown method {method_name!r} in object.')
if not getattr(method, 'no-args-flattening', False):
args = flatten(args)
if not getattr(method, 'no-second-level-holder-flattening', False):
args, kwargs = resolve_second_level_holders(args, kwargs)
state = ModuleState(self.interpreter)
# Many modules do for example self.interpreter.find_program_impl(),
# so we have to ensure they use the current interpreter and not the one
@ -798,7 +800,7 @@ class BuildTargetHolder(ObjectHolder[_BuildTarget]):
@property
def _target_object(self) -> build.BuildTarget:
if isinstance(self.held_object, build.BothLibraries):
return self.held_object.get_preferred_library()
return self.held_object.get_default_object()
assert isinstance(self.held_object, build.BuildTarget)
return self.held_object

@ -33,12 +33,14 @@ __all__ = [
'check_stringlist',
'default_resolve_key',
'flatten',
'resolve_second_level_holders',
'noPosargs',
'builtinMethodNoKwargs',
'noKwargs',
'stringArgs',
'noArgsFlattening',
'noSecondLevelHolderResolving',
'unholder_return',
'disablerIfNotFound',
'permittedKwargs',
@ -91,6 +93,7 @@ from .decorators import (
noKwargs,
stringArgs,
noArgsFlattening,
noSecondLevelHolderResolving,
unholder_return,
disablerIfNotFound,
permittedKwargs,
@ -115,5 +118,5 @@ from .exceptions import (
)
from .disabler import Disabler, is_disabled
from .helpers import check_stringlist, default_resolve_key, flatten
from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
from .interpreterbase import MesonVersionString, InterpreterBase

@ -14,7 +14,7 @@
from .. import mparser
from .exceptions import InvalidCode
from .helpers import flatten
from .helpers import flatten, resolve_second_level_holders
from ..mesonlib import HoldableObject
import typing as T
@ -57,6 +57,8 @@ class InterpreterObject:
method = self.methods[method_name]
if not getattr(method, 'no-args-flattening', False):
args = flatten(args)
if not getattr(method, 'no-second-level-holder-flattening', False):
args, kwargs = resolve_second_level_holders(args, kwargs)
return method(args, kwargs)
raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.')

@ -68,6 +68,10 @@ def noArgsFlattening(f: TV_func) -> TV_func:
setattr(f, 'no-args-flattening', True) # noqa: B010
return f
def noSecondLevelHolderResolving(f: TV_func) -> TV_func:
setattr(f, 'no-second-level-holder-flattening', True) # noqa: B010
return f
def unholder_return(f: TV_func) -> T.Callable[..., TYPE_var]:
@wraps(f)
def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .. import mparser, mlog
from .. import mesonlib, mparser, mlog
from .exceptions import InvalidArguments, InterpreterException
import collections.abc
@ -38,6 +38,17 @@ def flatten(args: T.Union['TYPE_var', T.List['TYPE_var']]) -> T.List['TYPE_var']
result.append(a)
return result
def resolve_second_level_holders(args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> T.Tuple[T.List['TYPE_var'], 'TYPE_kwargs']:
def resolver(arg: 'TYPE_var') -> 'TYPE_var':
if isinstance(arg, list):
return [resolver(x) for x in arg]
if isinstance(arg, dict):
return {k: resolver(v) for k, v in arg.items()}
if isinstance(arg, mesonlib.SecondLevelHolder):
return arg.get_default_object()
return arg
return [resolver(x) for x in args], {k: resolver(v) for k, v in kwargs.items()}
def check_stringlist(a: T.Any, msg: str = 'Arguments must be strings.') -> None:
if not isinstance(a, list):
mlog.debug('Not a list:', str(a))

@ -42,7 +42,7 @@ from .exceptions import (
from .decorators import FeatureNew, builtinMethodNoKwargs
from .disabler import Disabler, is_disabled
from .helpers import check_stringlist, default_resolve_key, flatten
from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
from ._unholder import _unholder
import os, copy, re
@ -546,6 +546,8 @@ The result of this is undefined and will become a hard error in a future Meson r
func_args = posargs
if not getattr(func, 'no-args-flattening', False):
func_args = flatten(posargs)
if not getattr(func, 'no-second-level-holder-flattening', False):
func_args, kwargs = resolve_second_level_holders(func_args, kwargs)
res = func(node, func_args, kwargs)
return self._holderify(res)
else:

@ -48,6 +48,7 @@ __all__ = [
'python_command',
'project_meson_versions',
'HoldableObject',
'SecondLevelHolder',
'File',
'FileMode',
'GitException',
@ -273,6 +274,14 @@ class HoldableObject(metaclass=abc.ABCMeta):
''' Dummy base class for all objects that can be
held by an interpreter.baseobjects.ObjectHolder '''
class SecondLevelHolder(HoldableObject, metaclass=abc.ABCMeta):
''' A second level object holder. The primary purpose
of such objects is to hold multiple objects with one
default option. '''
@abc.abstractmethod
def get_default_object(self) -> HoldableObject: ...
class FileMode:
# The first triad is for owner permissions, the second for group permissions,
# and the third for others (everyone else).

@ -494,8 +494,6 @@ class GnomeModule(ExtensionModule):
return cflags, internal_ldflags, external_ldflags, external_ldflags_nodedup, gi_includes
def _unwrap_gir_target(self, girtarget, state):
if isinstance(girtarget, build.BothLibraries):
girtarget = girtarget.get_preferred_library()
if not isinstance(girtarget, (build.Executable, build.SharedLibrary,
build.StaticLibrary)):
raise MesonException(f'Gir target must be an executable or library but is "{girtarget}" of type {type(girtarget).__name__}')

@ -22,9 +22,7 @@ from .. import mesonlib
from .. import mlog
from . import ModuleReturnValue
from . import ExtensionModule
from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs, TYPE_var, TYPE_kwargs
import typing as T
from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs
already_warned_objs = set()
@ -112,7 +110,6 @@ class DependenciesHelper:
def _process_libs(self, libs, public: bool):
libs = mesonlib.listify(libs)
libs = [x.get_preferred_library() if isinstance(x, build.BothLibraries) else x for x in libs]
processed_libs = []
processed_reqs = []
processed_cflags = []
@ -454,19 +451,6 @@ class PkgConfigModule(ExtensionModule):
if cflags and not dataonly:
ofile.write('Cflags: {}\n'.format(' '.join(cflags)))
@staticmethod
def _handle_both_libraries(args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[T.List[TYPE_var], TYPE_kwargs]:
def _do_extract(arg: TYPE_var) -> TYPE_var:
if isinstance(arg, list):
return [_do_extract(x) for x in arg]
elif isinstance(arg, dict):
return {k: _do_extract(v) for k, v in arg.items()}
elif isinstance(arg, build.BothLibraries):
return arg.get_preferred_library()
return arg
return [_do_extract(x) for x in args], {k: _do_extract(v) for k, v in kwargs.items()}
@FeatureNewKwargs('pkgconfig.generate', '0.59.0', ['unescaped_variables', 'unescaped_uninstalled_variables'])
@FeatureNewKwargs('pkgconfig.generate', '0.54.0', ['uninstalled_variables'])
@FeatureNewKwargs('pkgconfig.generate', '0.42.0', ['extra_cflags'])
@ -484,7 +468,6 @@ class PkgConfigModule(ExtensionModule):
default_name = None
mainlib = None
default_subdirs = ['.']
args, kwargs = PkgConfigModule._handle_both_libraries(args, kwargs)
if not args and 'version' not in kwargs:
FeatureNew.single_use('pkgconfig.generate implicit version keyword', '0.46.0', state.subproject)
elif len(args) == 1:

@ -34,8 +34,16 @@ exe_static2 = executable('prog-static2', 'main.c',
link_with : both_libs2.get_static_lib())
exe_both2 = executable('prog-both2', 'main.c', link_with : both_libs2)
# Test {set,get}_variable
set_variable('both_libs2', both_libs)
both_libs3 = get_variable('both_libs')
# Ensure that calling the build target methods also works
assert(both_libs.name() == 'mylib')
assert(both_libs2.name() == 'mylib')
assert(both_libs3.name() == 'mylib')
assert(both_libs2.get_shared_lib().name() == 'mylib')
assert(both_libs3.get_static_lib().name() == 'mylib')
test('runtest-shared-2', exe_shared2)
test('runtest-static-2', exe_static2)

Loading…
Cancel
Save