dependencies/hdf5: Convert to a dependency_factory

Instead of a mega dependency that does everything, use a dependency
factory for config-tool and pkg-config
pull/7758/head
Dylan Baker 4 years ago
parent dff40ca259
commit b7cb30e175
  1. 2
      .github/workflows/ci_frameworks.yml
  2. 4
      docs/markdown/Dependencies.md
  3. 6
      docs/markdown/snippets/hdf5_dependnecy_improvements.md
  4. 4
      mesonbuild/dependencies/__init__.py
  5. 6
      mesonbuild/dependencies/base.py
  6. 161
      mesonbuild/dependencies/hdf5.py
  7. 8
      test cases/frameworks/25 hdf5/meson.build
  8. 6
      test cases/frameworks/25 hdf5/meson_options.txt
  9. 10
      test cases/frameworks/25 hdf5/test.json

@ -46,7 +46,7 @@ jobs:
python-version: '3.x' python-version: '3.x'
- run: python -m pip install -e . - run: python -m pip install -e .
- run: brew install pkg-config ninja gcc hdf5 - run: brew install pkg-config ninja gcc hdf5
- run: meson setup "test cases/frameworks/25 hdf5" build - run: meson setup "test cases/frameworks/25 hdf5" build -Dmethod=config-tool
- run: meson compile -C build - run: meson compile -C build
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
if: failure() if: failure()

@ -364,6 +364,10 @@ language-specific, you must specify the requested language using the
Meson uses pkg-config to find HDF5. The standard low-level HDF5 function and the `HL` high-level HDF5 functions are linked for each language. Meson uses pkg-config to find HDF5. The standard low-level HDF5 function and the `HL` high-level HDF5 functions are linked for each language.
`method` may be `auto`, `config-tool` or `pkg-config`.
*New in 0.56.0* the `config-tool` method.
*New in 0.56.0* the dependencies now return proper dependency types and `get_variable` and similar methods should work as expected.
## libwmf ## libwmf

@ -0,0 +1,6 @@
## HDF5 dependency improvements
HDF5 has been improved so that the internal representations have been split.
This allows selecting pkg-config and config-tool dependencies separately.
Both work as proper dependencies of their type, so `get_variable` and similar
now work correctly.

@ -14,7 +14,7 @@
from .boost import BoostDependency from .boost import BoostDependency
from .cuda import CudaDependency from .cuda import CudaDependency
from .hdf5 import HDF5Dependency from .hdf5 import hdf5_factory
from .base import ( # noqa: F401 from .base import ( # noqa: F401
Dependency, DependencyException, DependencyMethods, ExternalProgram, EmptyExternalProgram, NonExistingExternalProgram, Dependency, DependencyException, DependencyMethods, ExternalProgram, EmptyExternalProgram, NonExistingExternalProgram,
ExternalDependency, NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency, ExternalDependency, NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency,
@ -51,7 +51,7 @@ packages.update({
# per-file # per-file
'coarray': coarray_factory, 'coarray': coarray_factory,
'hdf5': HDF5Dependency, 'hdf5': hdf5_factory,
'mpi': mpi_factory, 'mpi': mpi_factory,
'scalapack': scalapack_factory, 'scalapack': scalapack_factory,

@ -207,7 +207,7 @@ class Dependency:
""" """
raise RuntimeError('Unreachable code in partial_dependency called') raise RuntimeError('Unreachable code in partial_dependency called')
def _add_sub_dependency(self, deplist: T.List['DependencyType']) -> bool: def _add_sub_dependency(self, deplist: T.Iterable[T.Callable[[], 'Dependency']]) -> bool:
"""Add an internal depdency from a list of possible dependencies. """Add an internal depdency from a list of possible dependencies.
This method is intended to make it easier to add additional This method is intended to make it easier to add additional
@ -302,10 +302,10 @@ class InternalDependency(Dependency):
return new_dep return new_dep
class HasNativeKwarg: class HasNativeKwarg:
def __init__(self, kwargs): def __init__(self, kwargs: T.Dict[str, T.Any]):
self.for_machine = self.get_for_machine_from_kwargs(kwargs) self.for_machine = self.get_for_machine_from_kwargs(kwargs)
def get_for_machine_from_kwargs(self, kwargs): def get_for_machine_from_kwargs(self, kwargs: T.Dict[str, T.Any]) -> MachineChoice:
return MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST return MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST
class ExternalDependency(Dependency, HasNativeKwarg): class ExternalDependency(Dependency, HasNativeKwarg):

@ -14,53 +14,40 @@
# This file contains the detection logic for miscellaneous external dependencies. # This file contains the detection logic for miscellaneous external dependencies.
import functools
import subprocess import subprocess
import shutil import shutil
import re
from pathlib import Path from pathlib import Path
from .. import mlog from ..mesonlib import OrderedSet
from ..mesonlib import split_args, listify from .base import (
from .base import (DependencyException, DependencyMethods, ExternalDependency, ExternalProgram, DependencyException, DependencyMethods, ConfigToolDependency,
PkgConfigDependency) PkgConfigDependency, factory_methods
)
import typing as T import typing as T
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from .base import Dependency
from ..envconfig import MachineChoice
from ..environment import Environment from ..environment import Environment
class HDF5Dependency(ExternalDependency):
def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: class HDF5PkgConfigDependency(PkgConfigDependency):
language = kwargs.get('language', 'c')
super().__init__('hdf5', environment, kwargs, language=language)
kwargs['required'] = False
kwargs['silent'] = True
self.is_found = False
methods = listify(self.methods)
if language not in ('c', 'cpp', 'fortran'): """Handle brokenness in the HDF5 pkg-config files."""
raise DependencyException('Language {} is not supported with HDF5.'.format(language))
if set([DependencyMethods.AUTO, DependencyMethods.PKGCONFIG]).intersection(methods): def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
pkgconfig_files = ['hdf5', 'hdf5-serial'] language = language or 'c'
PCEXE = shutil.which('pkg-config') if language not in {'c', 'cpp', 'fortran'}:
if PCEXE: raise DependencyException('Language {} is not supported with HDF5.'.format(language))
# some distros put hdf5-1.2.3.pc with version number in .pc filename.
ret = subprocess.run([PCEXE, '--list-all'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
universal_newlines=True)
if ret.returncode == 0:
for pkg in ret.stdout.split('\n'):
if pkg.startswith(('hdf5')):
pkgconfig_files.append(pkg.split(' ', 1)[0])
pkgconfig_files = list(set(pkgconfig_files)) # dedupe
for pkg in pkgconfig_files: super().__init__(name, environment, kwargs, language)
pkgdep = PkgConfigDependency(pkg, environment, kwargs, language=self.language) if not self.is_found:
if not pkgdep.found(): return
continue
self.compile_args = pkgdep.get_compile_args()
# some broken pkgconfig don't actually list the full path to the needed includes # some broken pkgconfig don't actually list the full path to the needed includes
newinc = [] newinc = [] # type: T.List[str]
for arg in self.compile_args: for arg in self.compile_args:
if arg.startswith('-I'): if arg.startswith('-I'):
stem = 'static' if kwargs.get('static', False) else 'shared' stem = 'static' if kwargs.get('static', False) else 'shared'
@ -68,10 +55,8 @@ class HDF5Dependency(ExternalDependency):
newinc.append('-I' + str(Path(arg[2:]) / stem)) newinc.append('-I' + str(Path(arg[2:]) / stem))
self.compile_args += newinc self.compile_args += newinc
# derive needed libraries by language link_args = [] # type: T.List[str]
pd_link_args = pkgdep.get_link_args() for larg in self.get_link_args():
link_args = []
for larg in pd_link_args:
lpath = Path(larg) lpath = Path(larg)
# some pkg-config hdf5.pc (e.g. Ubuntu) don't include the commonly-used HL HDF5 libraries, # some pkg-config hdf5.pc (e.g. Ubuntu) don't include the commonly-used HL HDF5 libraries,
# so let's add them if they exist # so let's add them if they exist
@ -95,41 +80,83 @@ class HDF5Dependency(ExternalDependency):
link_args.append(larg) link_args.append(larg)
self.link_args = link_args self.link_args = link_args
self.version = pkgdep.get_version()
self.is_found = True
self.pcdep = pkgdep
return
if DependencyMethods.AUTO in methods:
wrappers = {'c': 'h5cc', 'cpp': 'h5c++', 'fortran': 'h5fc'} class HDF5ConfigToolDependency(ConfigToolDependency):
comp_args = []
link_args = [] """Wrapper around hdf5 binary config tools."""
# have to always do C as well as desired language
for lang in set([language, 'c']): version_arg = '-showconfig'
prog = ExternalProgram(wrappers[lang], silent=True)
if not prog.found(): def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
return language = language or 'c'
shlib_arg = '-noshlib' if kwargs.get('static', False) else '-shlib' if language not in {'c', 'cpp', 'fortran'}:
cmd = prog.get_command() + [shlib_arg, '-show'] raise DependencyException('Language {} is not supported with HDF5.'.format(language))
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, timeout=15)
if p.returncode != 0: if language == 'c':
mlog.debug('Command', mlog.bold(str(cmd)), 'failed to run:') tools = ['h5cc']
mlog.debug(mlog.bold('Standard output\n'), p.stdout) elif language == 'cpp':
mlog.debug(mlog.bold('Standard error\n'), p.stderr) tools = ['h5c++']
elif language == 'fortran':
tools = ['h5fc']
else:
raise DependencyException('How did you get here?')
# We need this before we call super()
for_machine = self.get_for_machine_from_kwargs(kwargs)
nkwargs = kwargs.copy()
nkwargs['tools'] = tools
super().__init__(name, environment, nkwargs, language)
if not self.is_found:
return return
args = split_args(p.stdout)
args = self.get_config_value(['-show', '-noshlib' if kwargs.get('static', False) else '-shlib'], 'args')
for arg in args[1:]: for arg in args[1:]:
if arg.startswith(('-I', '-f', '-D')) or arg == '-pthread': if arg.startswith(('-I', '-f', '-D')) or arg == '-pthread':
comp_args.append(arg) self.compile_args.append(arg)
elif arg.startswith(('-L', '-l', '-Wl')): elif arg.startswith(('-L', '-l', '-Wl')):
link_args.append(arg) self.link_args.append(arg)
elif Path(arg).is_file(): elif Path(arg).is_file():
link_args.append(arg) self.link_args.append(arg)
self.compile_args = comp_args
self.link_args = link_args # If the language is not C we need to add C as a subdependency
self.is_found = True if language != 'c':
return nkwargs = kwargs.copy()
nkwargs['language'] = 'c'
# I'm being too clever for mypy and pylint
self.is_found = self._add_sub_dependency(hdf5_factory(environment, self.for_machine, nkwargs)) # type: ignore # pylint: disable=no-value-for-parameter
def _sanitize_version(self, ver: str) -> str:
v = re.search(r'\s*HDF5 Version: (\d+\.\d+\.\d+)', ver)
return v.group(1)
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL})
def hdf5_factory(env: 'Environment', for_machine: 'MachineChoice',
kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List[T.Callable[[], 'Dependency']]:
language = kwargs.get('language')
candidates = [] # type: T.List[T.Callable[[], Dependency]]
if DependencyMethods.PKGCONFIG in methods:
# Use an ordered set so that these remain the first tried pkg-config files
pkgconfig_files = OrderedSet(['hdf5', 'hdf5-serial'])
# FIXME: This won't honor pkg-config paths, and cross-native files
PCEXE = shutil.which('pkg-config')
if PCEXE:
# some distros put hdf5-1.2.3.pc with version number in .pc filename.
ret = subprocess.run([PCEXE, '--list-all'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
universal_newlines=True)
if ret.returncode == 0:
for pkg in ret.stdout.split('\n'):
if pkg.startswith(('hdf5')):
pkgconfig_files.add(pkg.split(' ', 1)[0])
for pkg in pkgconfig_files:
candidates.append(functools.partial(HDF5PkgConfigDependency, pkg, env, kwargs, language))
if DependencyMethods.CONFIG_TOOL in methods:
candidates.append(functools.partial(HDF5ConfigToolDependency, 'hdf5', env, kwargs, language))
@staticmethod return candidates
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.AUTO, DependencyMethods.PKGCONFIG]

@ -2,8 +2,10 @@ project('hdf5_framework', 'c')
# NOTE: all HDF5 languages must have HDF5 C library working. # NOTE: all HDF5 languages must have HDF5 C library working.
method = get_option('method')
# --- C tests # --- C tests
h5c = dependency('hdf5', language : 'c', required : false) h5c = dependency('hdf5', language : 'c', required : false, method : method)
if not h5c.found() if not h5c.found()
error('MESON_SKIP_TEST: HDF5 C library not found.') error('MESON_SKIP_TEST: HDF5 C library not found.')
endif endif
@ -12,14 +14,14 @@ test('HDF5 C', exec, timeout: 30)
# --- C++ tests # --- C++ tests
if add_languages('cpp', required: false) if add_languages('cpp', required: false)
h5cpp = dependency('hdf5', language : 'cpp', required : false, disabler: true) h5cpp = dependency('hdf5', language : 'cpp', required : false, disabler: true, method : method)
execpp = executable('execpp', 'main.cpp', dependencies : h5cpp) execpp = executable('execpp', 'main.cpp', dependencies : h5cpp)
test('HDF5 C++', execpp, timeout: 30) test('HDF5 C++', execpp, timeout: 30)
endif endif
# --- Fortran tests # --- Fortran tests
if add_languages('fortran', required: false) if add_languages('fortran', required: false)
h5f = dependency('hdf5', language : 'fortran', required : false, disabler: true) h5f = dependency('hdf5', language : 'fortran', required : false, disabler: true, method : method)
exef = executable('exef', 'main.f90', dependencies : h5f) exef = executable('exef', 'main.f90', dependencies : h5f)
test('HDF5 Fortran', exef, timeout: 30) test('HDF5 Fortran', exef, timeout: 30)
endif endif

@ -0,0 +1,6 @@
option(
'method',
type : 'combo',
choices : ['pkg-config', 'config-tool'],
value : 'pkg-config'
)

@ -0,0 +1,10 @@
{
"matrix": {
"options": {
"method": [
{ "val": "pkg-config" },
{ "val": "config-tool" }
]
}
}
}
Loading…
Cancel
Save