diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 776703c48878..b741f6fb9bb9 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -674,6 +674,23 @@ hypothetical `testmod` module. tmod.do_something() ``` +### include() + +``` meson + void include(file, ...) +``` + +Executes the `file`. Once that is done, it returns and execution continues on the +line following this `include()` command. Variables defined in that +`file` are then available for use in later parts of the +current build file and in all subsequent build files executed with `subdir()`. + +This function has one keyword argument. + + - `if_found` takes one or several dependency objects and will only + execute the file if they all return `true` when queried with + `.found()` + ### include_directories() ``` meson @@ -1165,7 +1182,7 @@ the invocation. All steps executed up to this point are valid and will be executed by meson. This means that all targets defined before the call of `subdir_done` will be build. -If the current script was called by `subdir` the execution returns to the +If the current script was called by `subdir` or `include` the execution returns to the calling directory and continues as if the script had reached the end. If the current script is the top level script meson configures the project as defined up to this point. diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 7324f676018a..d958013492ab 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1666,6 +1666,7 @@ def get_cross_property_method(self, args, kwargs): 'executable': build.known_exe_kwargs, 'find_program': {'required', 'native'}, 'generator': {'arguments', 'output', 'depfile', 'capture', 'preserve_path_from'}, + 'include': {'if_found'}, 'include_directories': {'is_system'}, 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, 'install_headers': {'install_dir', 'subdir'}, @@ -1711,6 +1712,7 @@ def __init__(self, build, backend, subproject='', subdir='', subproject_dir='sub self.builtin.update({'meson': MesonMain(build, self)}) self.generators = [] self.visited_subdirs = {} + self.included_files = set() self.project_args_frozen = False self.global_args_frozen = False # implies self.project_args_frozen self.subprojects = {} @@ -1761,6 +1763,7 @@ def build_func_dict(self): 'files': self.func_files, 'find_library': self.func_find_library, 'find_program': self.func_find_program, + 'include': self.func_include, 'include_directories': self.func_include_directories, 'import': self.func_import, 'install_data': self.func_install_data, @@ -3226,6 +3229,45 @@ def func_configure_file(self, node, args, kwargs): self.build.data.append(build.Data([cfile], idir)) return mesonlib.File.from_built_file(self.subdir, output) + @permittedKwargs(permitted_kwargs['include']) + def func_include(self, node, args, kwargs): + self.validate_arguments(args, 1, [str]) + mesonlib.check_direntry_issues(args) + for i in mesonlib.extract_as_list(kwargs, 'if_found'): + if not hasattr(i, 'found_method'): + raise InterpreterException('Object used in if_found does not have a found method.') + if not i.found_method([], {}): + return + if os.path.isabs(args[0]): + absname = os.path.abspath(args[0]) + else: + absname = os.path.abspath(os.path.join(self.environment.get_source_dir(), self.subdir, args[0])) + prev_current_file = self.current_file + if absname in self.included_files: + raise InvalidArguments('Tried to include file "%s", which has already been included.' + % absname) + self.included_files.add(absname) + if absname not in self.build_def_files: + self.build_def_files.append(absname) + if not os.path.isfile(absname): + raise InterpreterException('Non-existent build file {!r}'.format(absname)) + with open(absname, encoding='utf8') as f: + code = f.read() + assert(isinstance(code, str)) + try: + codeblock = mparser.Parser(code, self.subdir).parse() + except mesonlib.MesonException as me: + me.file = self.current_file + raise me + self.current_file = absname + try: + self.evaluate_codeblock(codeblock) + except mesonlib.MesonException as me: + me.file = self.current_file + raise me + self.included_files.clear() + self.current_file = prev_current_file + @permittedKwargs(permitted_kwargs['include_directories']) @stringArgs def func_include_directories(self, node, args, kwargs): diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 60b046565573..8e54fd102d18 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -181,12 +181,14 @@ def __init__(self, source_root, subdir): self.funcs = {} self.builtin = {} self.subdir = subdir + self.current_file = '' self.variables = {} self.argument_depth = 0 self.current_lineno = -1 def load_root_meson_file(self): mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) + self.current_file = mesonfile if not os.path.isfile(mesonfile): raise InvalidArguments('Missing Meson file in %s' % mesonfile) with open(mesonfile, encoding='utf8') as mf: diff --git a/test cases/common/199 include/included_file b/test cases/common/199 include/included_file new file mode 100644 index 000000000000..669ba6664069 --- /dev/null +++ b/test cases/common/199 include/included_file @@ -0,0 +1,2 @@ +executable('test_include', 'main.c') + diff --git a/test cases/common/199 include/incr_variable b/test cases/common/199 include/incr_variable new file mode 100644 index 000000000000..8efbf9b13714 --- /dev/null +++ b/test cases/common/199 include/incr_variable @@ -0,0 +1 @@ +variable += 1 diff --git a/test cases/common/199 include/main.c b/test cases/common/199 include/main.c new file mode 100644 index 000000000000..d7ab4e54f7f8 --- /dev/null +++ b/test cases/common/199 include/main.c @@ -0,0 +1,5 @@ +int main(int argc, char **argv) +{ + return 0; +} + diff --git a/test cases/common/199 include/meson.build b/test cases/common/199 include/meson.build new file mode 100644 index 000000000000..c1a5815832b0 --- /dev/null +++ b/test cases/common/199 include/meson.build @@ -0,0 +1,18 @@ +# Should read instructions given in included_file + +project('example include', 'c') + +variable = 1 + +assert(variable == 1, 'variable doesn\'t have correct value') + +# Check whether it's possible to include the same file several times +include('incr_variable') +include('incr_variable') +include('incr_variable') + +assert(variable == 4, 'variable doesn\'t have correct value') + +# An executable is declared in included_file +include('included_file') + diff --git a/test cases/common/200 include if_found/included_file b/test cases/common/200 include if_found/included_file new file mode 100644 index 000000000000..1030e2551c4f --- /dev/null +++ b/test cases/common/200 include if_found/included_file @@ -0,0 +1 @@ +variable = 5 diff --git a/test cases/common/200 include if_found/meson.build b/test cases/common/200 include if_found/meson.build new file mode 100644 index 000000000000..f9b52df78525 --- /dev/null +++ b/test cases/common/200 include if_found/meson.build @@ -0,0 +1,8 @@ +project('subdir if found', 'c') + +not_found_dep = dependency('nonexisting', required : false) + +variable = 3 + +include('included_file', if_found : not_found_dep) +assert(variable == 3, 'included_file was unexpectedly included.') diff --git a/test cases/failing/75 missing included file/meson.build b/test cases/failing/75 missing included file/meson.build new file mode 100644 index 000000000000..343c8eb0d1f5 --- /dev/null +++ b/test cases/failing/75 missing included file/meson.build @@ -0,0 +1,6 @@ +# Should fail because included_file is missing + +project('missing included file', 'c') + +include('included_file') + diff --git a/test cases/failing/76 included file detected cyclic dependency/included_file1 b/test cases/failing/76 included file detected cyclic dependency/included_file1 new file mode 100644 index 000000000000..3a3fa76099f9 --- /dev/null +++ b/test cases/failing/76 included file detected cyclic dependency/included_file1 @@ -0,0 +1 @@ +include('included_file2') diff --git a/test cases/failing/76 included file detected cyclic dependency/included_file2 b/test cases/failing/76 included file detected cyclic dependency/included_file2 new file mode 100644 index 000000000000..17eb93dfc2ec --- /dev/null +++ b/test cases/failing/76 included file detected cyclic dependency/included_file2 @@ -0,0 +1 @@ +include('included_file1') diff --git a/test cases/failing/76 included file detected cyclic dependency/meson.build b/test cases/failing/76 included file detected cyclic dependency/meson.build new file mode 100644 index 000000000000..56f28a533fe1 --- /dev/null +++ b/test cases/failing/76 included file detected cyclic dependency/meson.build @@ -0,0 +1,7 @@ +# Should fail because included_file1 includes included_file2 and +# included_file2 includes included_file1 + +project('included file detected cyclic dependency', 'c') + +include('included_file2') +