Skip to content

Commit

Permalink
Merge pull request #3218 from mesonbuild/findoverrider
Browse files Browse the repository at this point in the history
Make it possible to override find_program [skip ci]
  • Loading branch information
jpakkane committed Apr 16, 2018
2 parents 5e45f4c + 6cdd14f commit 0e00470
Show file tree
Hide file tree
Showing 32 changed files with 299 additions and 90 deletions.
7 changes: 7 additions & 0 deletions docs/markdown/Reference-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,13 @@ the following methods.
/path/to/meson.py introspect`. The user is responsible for splitting
the string to an array if needed.

- `override_find_program(progname, program)` [*(Added
0.46.0)*](Release-notes-for-0-46-0.html#Can-override-find_program)
specifies that whenever `find_program` is used to find a program
named `progname`, Meson should not not look it up on the system but
instead return `program`, which may either be the result of
`find_program` or `configure_file`.

- `project_version()` returns the version string specified in `project` function call.

- `project_license()` returns the array of licenses specified in `project` function call.
Expand Down
37 changes: 37 additions & 0 deletions docs/markdown/snippets/find-override.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## Can override find_program

It is now possible to override the result of `find_program` to point
to a custom program you want. The overriding is global and applies to
every subproject from there on. Here is how you would use it.

In master project

```meson
subproject('mydep')
```

In the called subproject:

```meson
prog = find_program('my_custom_script')
meson.override_find_program('mycodegen', prog)
```

In master project (or, in fact, any subproject):

```meson
genprog = find_program('mycodegen')
```

Now `genprog` points to the custom script. If the dependency had come
from the system, then it would point to the system version.

You can also use the return value of `configure_file()` to override
a program in the same way as above:

```meson
prog_script = configure_file(input : 'script.sh.in',
output : 'script.sh',
configuration : cdata)
meson.override_find_program('mycodegen', prog_script)
```
2 changes: 2 additions & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ def __init__(self, environment):
self.dep_manifest = {}
self.cross_stdlibs = {}
self.test_setups = {}
self.find_overrides = {}
self.searched_programs = set() # The list of all programs that have been searched for.

def add_compiler(self, compiler):
if self.static_linker is None and compiler.needs_static_linker():
Expand Down
1 change: 0 additions & 1 deletion mesonbuild/coredata.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ def __init__(self, options):
self.compilers = OrderedDict()
self.cross_compilers = OrderedDict()
self.deps = OrderedDict()
self.modules = {}
# Only to print a warning if it changes between Meson invocations.
self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '')

Expand Down
110 changes: 85 additions & 25 deletions mesonbuild/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,7 @@ def __init__(self, build, interpreter):
'add_install_script': self.add_install_script_method,
'add_postconf_script': self.add_postconf_script_method,
'install_dependency_manifest': self.install_dependency_manifest_method,
'override_find_program': self.override_find_program_method,
'project_version': self.project_version_method,
'project_license': self.project_license_method,
'version': self.version_method,
Expand Down Expand Up @@ -1416,6 +1417,26 @@ def install_dependency_manifest_method(self, args, kwargs):
raise InterpreterException('Argument must be a string.')
self.build.dep_manifest_name = args[0]

def override_find_program_method(self, args, kwargs):
if len(args) != 2:
raise InterpreterException('Override needs two arguments')
name = args[0]
exe = args[1]
if not isinstance(name, str):
raise InterpreterException('First argument must be a string')
if hasattr(exe, 'held_object'):
exe = exe.held_object
if isinstance(exe, mesonlib.File):
abspath = exe.absolute_path(self.interpreter.environment.source_dir,
self.interpreter.environment.build_dir)
if not os.path.exists(abspath):
raise InterpreterException('Tried to override %s with a file that does not exist.' % name)
exe = dependencies.ExternalProgram(abspath)
if not isinstance(exe, dependencies.ExternalProgram):
# FIXME, make this work if the exe is an Executable target.
raise InterpreterException('Second argument must be an external program.')
self.interpreter.add_find_program_override(name, exe)

def project_version_method(self, args, kwargs):
return self.build.dep_manifest[self.interpreter.active_projectname]['version']

Expand Down Expand Up @@ -1493,14 +1514,18 @@ def get_cross_property_method(self, args, kwargs):
class Interpreter(InterpreterBase):

def __init__(self, build, backend, subproject='', subdir='', subproject_dir='subprojects',
default_project_options=[]):
modules = None, default_project_options=[]):
super().__init__(build.environment.get_source_dir(), subdir)
self.an_unpicklable_object = mesonlib.an_unpicklable_object
self.build = build
self.environment = build.environment
self.coredata = self.environment.get_coredata()
self.backend = backend
self.subproject = subproject
if modules is None:
self.modules = {}
else:
self.modules = modules
# Subproject directory is usually the name of the subproject, but can
# be different for dependencies provided by wrap files.
self.subproject_directory_name = subdir.split(os.path.sep)[-1]
Expand Down Expand Up @@ -1681,13 +1706,13 @@ def func_import(self, node, args, kwargs):
plainname = modname.split('-', 1)[1]
mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node)
modname = 'unstable_' + plainname
if modname not in self.environment.coredata.modules:
if modname not in self.modules:
try:
module = importlib.import_module('mesonbuild.modules.' + modname)
except ImportError:
raise InvalidArguments('Module "%s" does not exist' % (modname, ))
self.environment.coredata.modules[modname] = module.initialize()
return ModuleHolder(modname, self.environment.coredata.modules[modname], self)
self.modules[modname] = module.initialize(self)
return ModuleHolder(modname, self.modules[modname], self)

@stringArgs
@noKwargs
Expand Down Expand Up @@ -1864,15 +1889,16 @@ def do_subproject(self, dirname, kwargs):
self.global_args_frozen = True
mlog.log()
with mlog.nested():
mlog.log('Executing subproject ', mlog.bold(dirname), '.\n', sep='')
mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='')
subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir,
mesonlib.stringlistify(kwargs.get('default_options', [])))
self.modules, mesonlib.stringlistify(kwargs.get('default_options', [])))
subi.subprojects = self.subprojects

subi.subproject_stack = self.subproject_stack + [dirname]
current_active = self.active_projectname
subi.run()
mlog.log('\nSubproject', mlog.bold(dirname), 'finished.')

if 'version' in kwargs:
pv = subi.project_version
wanted = kwargs['version']
Expand Down Expand Up @@ -2225,7 +2251,7 @@ def add_base_options(self, compiler):
break
self.coredata.base_options[optname] = oobj

def program_from_cross_file(self, prognames):
def program_from_cross_file(self, prognames, silent=False):
bins = self.environment.cross_info.config['binaries']
for p in prognames:
if hasattr(p, 'held_object'):
Expand All @@ -2236,11 +2262,11 @@ def program_from_cross_file(self, prognames):
raise InterpreterException('Executable name must be a string.')
if p in bins:
exename = bins[p]
extprog = dependencies.ExternalProgram(exename)
extprog = dependencies.ExternalProgram(exename, silent=silent)
progobj = ExternalProgramHolder(extprog)
return progobj

def program_from_system(self, args):
def program_from_system(self, args, silent=False):
# Search for scripts relative to current subdir.
# Do not cache found programs because find_program('foobar')
# might give different results when run from different source dirs.
Expand All @@ -2259,32 +2285,66 @@ def program_from_system(self, args):
else:
raise InvalidArguments('find_program only accepts strings and '
'files, not {!r}'.format(exename))
extprog = dependencies.ExternalProgram(exename, search_dir=search_dir)
extprog = dependencies.ExternalProgram(exename, search_dir=search_dir,
silent=silent)
progobj = ExternalProgramHolder(extprog)
if progobj.found():
return progobj

def program_from_overrides(self, command_names, silent=False):
for name in command_names:
if not isinstance(name, str):
continue
if name in self.build.find_overrides:
exe = self.build.find_overrides[name]
if not silent:
mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
'(overridden: %s)' % ' '.join(exe.command))
return ExternalProgramHolder(exe)
return None

def store_name_lookups(self, command_names):
for name in command_names:
if isinstance(name, str):
self.build.searched_programs.add(name)

def add_find_program_override(self, name, exe):
if name in self.build.searched_programs:
raise InterpreterException('Tried to override finding of executable "%s" which has already been found.'
% name)
if name in self.build.find_overrides:
raise InterpreterException('Tried to override executable "%s" which has already been overridden.'
% name)
self.build.find_overrides[name] = exe

def find_program_impl(self, args, native=False, required=True, silent=True):
if not isinstance(args, list):
args = [args]
progobj = self.program_from_overrides(args, silent=silent)
if progobj is None and self.build.environment.is_cross_build():
if not native:
progobj = self.program_from_cross_file(args, silent=silent)
if progobj is None:
progobj = self.program_from_system(args, silent=silent)
if required and (progobj is None or not progobj.found()):
raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args))
if progobj is None:
return ExternalProgramHolder(dependencies.NonExistingExternalProgram())
# Only store successful lookups
self.store_name_lookups(args)
return progobj

@permittedKwargs(permitted_kwargs['find_program'])
def func_find_program(self, node, args, kwargs):
if not args:
raise InterpreterException('No program name specified.')
required = kwargs.get('required', True)
if not isinstance(required, bool):
raise InvalidArguments('"required" argument must be a boolean.')
progobj = None
if self.build.environment.is_cross_build():
use_native = kwargs.get('native', False)
if not isinstance(use_native, bool):
raise InvalidArguments('Argument to "native" must be a boolean.')
if not use_native:
progobj = self.program_from_cross_file(args)
if progobj is None:
progobj = self.program_from_system(args)
if required and (progobj is None or not progobj.found()):
raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args))
if progobj is None:
return ExternalProgramHolder(dependencies.NonExistingExternalProgram())
return progobj
use_native = kwargs.get('native', False)
if not isinstance(use_native, bool):
raise InvalidArguments('Argument to "native" must be a boolean.')
return self.find_program_impl(args, native=use_native, required=required, silent=False)

def func_find_library(self, node, args, kwargs):
raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n'
Expand Down Expand Up @@ -2691,7 +2751,7 @@ def add_test(self, node, args, kwargs, is_base_test):
exe = args[1]
if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)):
if isinstance(exe, mesonlib.File):
exe = self.func_find_program(node, (args[1], ), {})
exe = self.func_find_program(node, args[1], {})
else:
raise InterpreterException('Second argument must be executable.')
par = kwargs.get('is_parallel', True)
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/mesonmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ def _generate(self, env):
# Post-conf scripts must be run after writing coredata or else introspection fails.
g.run_postconf_scripts()
except:
os.unlink(cdf)
if 'cdf' in locals():
os.unlink(cdf)
raise

def run_script_command(args):
Expand Down
15 changes: 2 additions & 13 deletions mesonbuild/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,15 @@ def wrapped(s, interpreter, state, args, kwargs):
return f(s, interpreter, state, args, kwargs)
return wrapped

_found_programs = {}


class ExtensionModule:
def __init__(self):
def __init__(self, interpreter):
self.interpreter = interpreter
self.snippets = set() # List of methods that operate only on the interpreter.

def is_snippet(self, funcname):
return funcname in self.snippets

def find_program(program_name, target_name):
if program_name in _found_programs:
return _found_programs[program_name]
program = dependencies.ExternalProgram(program_name)
if not program.found():
m = "Target {!r} can't be generated as {!r} could not be found"
raise MesonException(m.format(target_name, program_name))
_found_programs[program_name] = program
return program


def get_include_args(include_dirs, prefix='-I'):
'''
Expand Down
Loading

0 comments on commit 0e00470

Please sign in to comment.