Skip to content

Commit

Permalink
Merge pull request #2512 from dcbaker/wip/config-tool-variables
Browse files Browse the repository at this point in the history
Add method to get values from config tool based dependency
  • Loading branch information
jpakkane committed Nov 28, 2017
2 parents 8a1a866 + a52c22d commit 746e70c
Show file tree
Hide file tree
Showing 14 changed files with 376 additions and 283 deletions.
28 changes: 9 additions & 19 deletions docs/markdown/Dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,32 +178,22 @@ the list of sources for the target. The `modules` keyword of
`dependency` works just like it does with Boost. It tells which
subparts of Qt the program uses.

## Pcap
## Dependencies using config tools

The pcap library does not ship with pkg-config at the time or writing
but instead it has its own `pcap-config` util. Meson will use it
automatically:
CUPS, LLVM, PCAP, WxWidgets, libwmf, and GnuStep either do not provide
pkg-config modules or additionally can be detected via a config tool
(cups-config, llvm-config, etc). Meson has native support for these tools, and
then can be found like other dependencies:

```meson
pcap_dep = dependency('pcap', version : '>=1.0')
```

## CUPS

The cups library does not ship with pkg-config at the time or writing
but instead it has its own `cups-config` util. Meson will use it
automatically:

```meson
cups_dep = dependency('cups', version : '>=1.4')
llvm_dep = dependency('llvm', version : '>=4.0')
```

## LibWMF

The libwmf library does not ship with pkg-config at the time or writing
but instead it has its own `libwmf-config` util. Meson will use it
automatically:
Some of these tools (like wmf and cups) provide both pkg-config and config
tools support. You can force one or another via the method keyword:

```meson
libwmf_dep = dependency('libwmf', version : '>=0.2.8')
wmf_dep = dependency('wmf', method : 'config-tool')
```
4 changes: 4 additions & 0 deletions docs/markdown/Reference-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,10 @@ an external dependency with the following methods:
pkg-config variable specified, or, if invoked on a non pkg-config
dependency, error out

- `get_configtool_variable(varname)` (*Added 0.44.0*) will get the
command line argument from the config tool (with `--` prepended), or,
if invoked on a non config-tool dependency, error out.

- `type_name()` which returns a string describing the type of the
dependency, the most common values are `internal` for deps created
with `declare_dependencies` and `pkgconfig` for system dependencies
Expand Down
11 changes: 11 additions & 0 deletions docs/markdown/snippets/config-tool-variable-method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Config-Tool based dependencies gained a method to get arbitrary options

A number of dependencies (CUPS, LLVM, pcap, WxWidgets, GnuStep) use a config
tool instead of pkg-config. As of this version they now have a
`get_configtool_variable` method, which is analogous to the
`get_pkgconfig_variable` for pkg config.

```meson
dep_llvm = dependency('LLVM')
llvm_inc_dir = dep_llvm.get_configtool_variable('includedir')
```
156 changes: 146 additions & 10 deletions mesonbuild/dependencies/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
import stat
import shlex
import shutil
import textwrap
from enum import Enum

from .. import mlog
from .. import mesonlib
from ..mesonlib import MesonException, Popen_safe, version_compare_many, listify
from ..mesonlib import (
MesonException, Popen_safe, version_compare_many, version_compare, listify
)


# These must be defined in this file to avoid cyclical references.
Expand All @@ -43,18 +46,17 @@ class DependencyMethods(Enum):
QMAKE = 'qmake'
# Just specify the standard link arguments, assuming the operating system provides the library.
SYSTEM = 'system'
# Detect using sdl2-config
SDLCONFIG = 'sdlconfig'
# Detect using pcap-config
PCAPCONFIG = 'pcap-config'
# Detect using cups-config
CUPSCONFIG = 'cups-config'
# Detect using libwmf-config
LIBWMFCONFIG = 'libwmf-config'
# This is only supported on OSX - search the frameworks directory by name.
EXTRAFRAMEWORK = 'extraframework'
# Detect using the sysconfig module.
SYSCONFIG = 'sysconfig'
# Specify using a "program"-config style tool
CONFIG_TOOL = 'config-tool'
# For backewards compatibility
SDLCONFIG = 'sdlconfig'
CUPSCONFIG = 'cups-config'
PCAPCONFIG = 'pcap-config'
LIBWMFCONFIG = 'libwmf-config'


class Dependency:
Expand All @@ -72,6 +74,16 @@ def __init__(self, type_name, kwargs):
raise DependencyException('method {!r} is invalid'.format(method))
method = DependencyMethods(method)

# This sets per-too config methods which are deprecated to to the new
# generic CONFIG_TOOL value.
if method in [DependencyMethods.SDLCONFIG, DependencyMethods.CUPSCONFIG,
DependencyMethods.PCAPCONFIG, DependencyMethods.LIBWMFCONFIG]:
mlog.warning(textwrap.dedent("""\
Configuration method {} has been deprecated in favor of
'config-tool'. This will be removed in a future version of
meson.""".format(method)))
method = DependencyMethods.CONFIG_TOOL

# Set the detection method. If the method is set to auto, use any available method.
# If method is set to a specific string, allow only that detection method.
if method == DependencyMethods.AUTO:
Expand All @@ -82,7 +94,7 @@ def __init__(self, type_name, kwargs):
raise DependencyException(
'Unsupported detection method: {}, allowed methods are {}'.format(
method.value,
mlog.format_list(map(lambda x: x.value, [DependencyMethods.AUTO] + self.get_methods()))))
mlog.format_list([x.value for x in [DependencyMethods.AUTO] + self.get_methods()])))

def __repr__(self):
s = '<{0} {1}: {2}>'
Expand Down Expand Up @@ -120,6 +132,9 @@ def need_threads(self):
def get_pkgconfig_variable(self, variable_name):
raise NotImplementedError('{!r} is not a pkgconfig dependency'.format(self.name))

def get_configtool_variable(self, variable_name):
raise NotImplementedError('{!r} is not a config-tool dependency'.format(self.name))


class InternalDependency(Dependency):
def __init__(self, version, incdirs, compile_args, link_args, libraries, sources, ext_deps):
Expand Down Expand Up @@ -167,6 +182,127 @@ def get_compiler(self):
return self.compiler


class ConfigToolDependency(ExternalDependency):

"""Class representing dependencies found using a config tool."""

tools = None
tool_name = None

def __init__(self, name, environment, language, kwargs):
super().__init__('config-tool', environment, language, kwargs)
self.name = name
self.tools = listify(kwargs.get('tools', self.tools))

req_version = kwargs.get('version', None)
tool, version = self.find_config(req_version)
self.config = tool
self.is_found = self.report_config(version, req_version)
if not self.is_found:
self.config = None
return
self.version = version

@classmethod
def factory(cls, name, environment, language, kwargs, tools, tool_name):
"""Constructor for use in dependencies that can be found multiple ways.
In addition to the standard constructor values, this constructor sets
the tool_name and tools values of the instance.
"""
# This deserves some explanation, because metaprogramming is hard.
# This uses type() to create a dynamic subclass of ConfigToolDependency
# with the tools and tool_name class attributes set, this class is then
# instantiated and returned. The reduce function (method) is also
# attached, since python's pickle module won't be able to do anything
# with this dynamically generated class otherwise.
def reduce(_):
return (cls.factory,
(name, environment, language, kwargs, tools, tool_name))
sub = type('{}Dependency'.format(name.capitalize()), (cls, ),
{'tools': tools, 'tool_name': tool_name, '__reduce__': reduce})

return sub(name, environment, language, kwargs)

def find_config(self, versions=None):
"""Helper method that searchs for config tool binaries in PATH and
returns the one that best matches the given version requirements.
"""
if not isinstance(versions, list) and versions is not None:
versions = listify(versions)

best_match = (None, None)
for tool in self.tools:
try:
p, out = Popen_safe([tool, '--version'])[:2]
except (FileNotFoundError, PermissionError):
continue
if p.returncode != 0:
continue

out = out.strip()
# Some tools, like pcap-config don't supply a version, but also
# dont fail with --version, in that case just assume that there is
# only one verison and return it.
if not out:
return (tool, 'none')
if versions:
is_found = version_compare_many(out, versions)[0]
# This allows returning a found version without a config tool,
# which is useful to inform the user that you found version x,
# but y was required.
if not is_found:
tool = None
if best_match[1]:
if version_compare(out, '> {}'.format(best_match[1])):
best_match = (tool, out)
else:
best_match = (tool, out)

return best_match

def report_config(self, version, req_version):
"""Helper method to print messages about the tool."""
if self.config is None:
if version is not None:
mlog.log('found {} {!r} but need:'.format(self.tool_name, version),
req_version)
else:
mlog.log("No {} found; can't detect dependency".format(self.tool_name))
mlog.log('Dependency {} found:'.format(self.name), mlog.red('NO'))
if self.required:
raise DependencyException('Dependency {} not found'.format(self.name))
return False
mlog.log('Found {}:'.format(self.tool_name), mlog.bold(shutil.which(self.config)),
'({})'.format(version))
mlog.log('Dependency {} found:'.format(self.name), mlog.green('YES'))
return True

def get_config_value(self, args, stage):
p, out, err = Popen_safe([self.config] + args)
if p.returncode != 0:
if self.required:
raise DependencyException(
'Could not generate {} for {}.\n{}'.format(
stage, self.name, err))
return []
return shlex.split(out)

def get_methods(self):
return [DependencyMethods.AUTO, DependencyMethods.CONFIG_TOOL]

def get_configtool_variable(self, variable_name):
p, out, _ = Popen_safe([self.config, '--{}'.format(variable_name)])
if p.returncode != 0:
if self.required:
raise DependencyException(
'Could not get variable "{}" for dependency {}'.format(
variable_name, self.name))
variable = out.strip()
mlog.debug('Got config-tool variable {} : {}'.format(variable_name, variable))
return variable


class PkgConfigDependency(ExternalDependency):
# The class's copy of the pkg-config path. Avoids having to search for it
# multiple times in the same Meson invocation.
Expand Down

0 comments on commit 746e70c

Please sign in to comment.