diff options
25 files changed, 299 insertions, 149 deletions
diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile index 3789e8d..5503227 100644 --- a/ciimage/Dockerfile +++ b/ciimage/Dockerfile @@ -9,5 +9,7 @@ RUN apt-get -y update && apt-get -y upgrade \ && apt-get -y install llvm libsdl2-dev \ && apt-get -y install python3-pip libxml2-dev libxslt1-dev cmake libyaml-dev \ && apt-get -y install openmpi-bin libopenmpi-dev \ +&& apt-get -y install libboost-log-dev \ && apt-get -y install libvulkan-dev libpcap-dev \ +&& apt-get -y install gcovr lcov \ && python3 -m pip install hotdoc codecov diff --git a/docs/markdown/Porting-from-autotools.md b/docs/markdown/Porting-from-autotools.md index 91ed5d2..5786e0e 100644 --- a/docs/markdown/Porting-from-autotools.md +++ b/docs/markdown/Porting-from-autotools.md @@ -578,7 +578,7 @@ introspection_sources = [ 'as-tag.c', 'as-tag.h', 'as-utils.c', - 'as-utils.h'] + 'as-utils.h', 'as-version.h'] gnome.generate_gir(asglib, diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 901535b..c98bd79 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -68,10 +68,10 @@ Like `add_project_arguments` but the arguments are passed to the linker. Add a custom test setup that can be used to run the tests with a custom setup, for example under Valgrind. The keyword arguments are the following: +- `env` an [environment object](#environment-object) to use a custom environment - `exe_wrapper` a list containing the wrapper command or script followed by the arguments to it - `gdb` if `true`, the tests are also run under `gdb` - `timeout_multiplier` a number to multiply the test timeout with -- `env` an [environment object](#environment-object) to use a custom environment To use the test setup, run `mesontest --setup=*name*` inside the build dir. @@ -123,12 +123,21 @@ When a list of strings is passed to the `command:` keyword argument, it takes an These are all the supported keyword arguments: -- `input` the input file name. If it's not specified in configuration mode, all the variables in the `configuration:` object (see above) are written to the `output:` file. -- `output` the output file name (since v0.41.0, may contain `@PLAINNAME@` or `@BASENAME@` substitutions). In configuration mode, the permissions of the input file (if it is specified) are copied to the output file. -- `configuration` as explained above, this is where you pass the configuration data object as returned by `configuration_data()` -- `command` as explained above, if specified, Meson does not create the file itself but rather runs the specified command, which allows you to do fully custom file generation -- `capture` when this argument is set to true, Meson captures `stdout` of the `command` and writes it to the target file specified as `output`. Available since v0.41.0. -- `install_dir` the subdirectory to install the generated file to (e.g. `share/myproject`), if omitted the file is not installed. +- `capture` when this argument is set to true, Meson captures `stdout` + of the `command` and writes it to the target file specified as + `output`. Available since v0.41.0. +- `command` as explained above, if specified, Meson does not create + the file itself but rather runs the specified command, which allows + you to do fully custom file generation +- `input` the input file name. If it's not specified in configuration + mode, all the variables in the `configuration:` object (see above) + are written to the `output:` file. +- `install_dir` the subdirectory to install the generated file to + (e.g. `share/myproject`), if omitted the file is not installed. +- `output` the output file name (since v0.41.0, may contain + `@PLAINNAME@` or `@BASENAME@` substitutions). In configuration mode, + the permissions of the input file (if it is specified) are copied to + the output file. ### custom_target() @@ -136,30 +145,63 @@ These are all the supported keyword arguments: customtarget custom_target(*name*, ...) ``` -Create a custom top level build target. The only positional argument is the name of this target and the keyword arguments are the following. - +Create a custom top level build target. The only positional argument +is the name of this target and the keyword arguments are the +following. + +- `build_by_default` *(added 0.38.0)* causes, when set to true, to + have this target be built by default, that is, when invoking plain + `ninja`; the default value is false +- `build_always` if `true` this target is always considered out of + date and is rebuilt every time, useful for things such as build + timestamps or revision control tags +- `capture`, there are some compilers that can't be told to write + their output to a file but instead write it to standard output. When + this argument is set to true, Meson captures `stdout` and writes it + to the target file. Note that your command argument list may not + contain `@OUTPUT@` when capture mode is active. +- `command` command to run to create outputs from inputs. The command + may be strings or the return of `find_program()` or `executable()` + (note: always specify commands in array form `['commandname', + '-arg1', '-arg2']` rather than as a string `'commandname -arg1 + -arg2'` as the latter will *not* work) +- `depend_files` files ([`string`](#string-object), + [`files()`](#files), or [`configure_file()`](#configure_file)) that + this target depends on but are not listed in the `command` keyword + argument. Useful for adding regen dependencies. +- `depends` specifies that this target depends on the specified + target(s), even though it does not take any of them as a command + line argument. This is meant for cases where you have a tool that + e.g. does globbing internally. Usually you should just put the + generated sources as inputs and Meson will set up all dependencies + automatically. +- `depfile` is a dependency file that the command can write listing + all the additional files this target depends on, for example a C + compiler would list all the header files it included, and a change + in any one of these files triggers a recompilation - `input` list of source files. As of 0.41.0 the list will be flattened. -- `output` list of output files -- `command` command to run to create outputs from inputs. The command may be strings or the return of `find_program()` or `executable()` (note: always specify commands in array form `['commandname', '-arg1', '-arg2']` rather than as a string `'commandname -arg1 -arg2'` as the latter will *not* work) - `install` when true, this target is installed during the install step - `install_dir` directory to install to -- `build_always` if `true` this target is always considered out of date and is rebuilt every time, useful for things such as build timestamps or revision control tags -- `capture`, there are some compilers that can't be told to write their output to a file but instead write it to standard output. When this argument is set to true, Meson captures `stdout` and writes it to the target file. Note that your command argument list may not contain `@OUTPUT@` when capture mode is active. -- `depends` specifies that this target depends on the specified target(s), even though it does not take any of them as a command line argument. This is meant for cases where you have a tool that e.g. does globbing internally. Usually you should just put the generated sources as inputs and Meson will set up all dependencies automatically. -- `depend_files` files ([`string`](#string-object), [`files()`](#files), or [`configure_file()`](#configure_file)) that this target depends on but are not listed in the `command` keyword argument. Useful for adding regen dependencies. -- `depfile` is a dependency file that the command can write listing all the additional files this target depends on, for example a C compiler would list all the header files it included, and a change in any one of these files triggers a recompilation -- `build_by_default` *(added 0.38.0)* causes, when set to true, to have this target be built by default, that is, when invoking plain `ninja`; the default value is false - -The list of strings passed to the `command` keyword argument accept the following special string substitutions: +- `output` list of output files -- `@INPUT@` the full path to the input passed to `input`. If more than one input is specified, all of them will be substituted as separate arguments only if the command uses `'@INPUT@'` as a standalone-argument. For instance, this would not work: `command : ['cp', './@INPUT@']`, but this would: `command : ['cp', '@INPUT@']`. -- `@OUTPUT@` the full path to the output passed to `output`. If more than one outputs are specified, the behavior is the same as `@INPUT@`. +The list of strings passed to the `command` keyword argument accept +the following special string substitutions: + +- `@INPUT@` the full path to the input passed to `input`. If more than + one input is specified, all of them will be substituted as separate + arguments only if the command uses `'@INPUT@'` as a + standalone-argument. For instance, this would not work: `command : + ['cp', './@INPUT@']`, but this would: `command : ['cp', '@INPUT@']`. +- `@OUTPUT@` the full path to the output passed to `output`. If more + than one outputs are specified, the behavior is the same as + `@INPUT@`. - `@INPUT0@` `@INPUT1@` `...` the full path to the input with the specified array index in `input` - `@OUTPUT0@` `@OUTPUT1@` `...` the full path to the output with the specified array index in `output` - `@OUTDIR@` the full path to the directory where the output(s) must be written - `@DEPFILE@` the full path to the dependency file passed to `depfile` -The returned object also has methods that are documented in the [object methods section](#custom-target-object) below. +The returned object also has methods that are documented in the +[object methods section](#custom-target-object) below. ### declare_dependency() @@ -543,7 +585,7 @@ This function prints its argument to stdout. void project(project_name, list_of_languages, ...) ``` -The first argument to this function must be a string defining the name of this project. It must be followed by one or more programming languages that the project uses. Supported values for languages are `c`, `cpp` (for `C++`), `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`) and `vala`. +The first argument to this function must be a string defining the name of this project. It is followed by programming languages that the project uses. Supported values for languages are `c`, `cpp` (for `C++`), `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`) and `vala`. In versions before `0.40.0` you must have at least one language listed. The project name can be any string you want, it's not used for anything except descriptive purposes. However since it is written to e.g. the dependency manifest is usually makes sense to have it be the same as the project tarball or pkg-config name. So for example you would probably want to use the name _libfoobar_ instead of _The Foobar Library_. diff --git a/docs/markdown/Wrap-best-practices-and-tips.md b/docs/markdown/Wrap-best-practices-and-tips.md index cebc779..b164ec5 100644 --- a/docs/markdown/Wrap-best-practices-and-tips.md +++ b/docs/markdown/Wrap-best-practices-and-tips.md @@ -61,3 +61,41 @@ executable('dep_using_exe', 'main.c', ``` Meson will ensure that the header file has been built before compiling `main.c`. + +## Avoid exposing compilable source files in declare_dependency + +The main use for the `sources` argument in `declare_dependency` is to construct the correct dependency graph for the backends, as demonstrated in the previous section. It is extremely important to note that it should *not* be used to directly expose compilable sources (`.c`, `.cpp`, etc.) of dependencies, and should rather only be used for header/config files. The following example will illustrate what can go wrong if you accidentally expose compilable source files. + +So you've read about unity builds and how Meson natively supports them. You decide to expose the sources of dependencies in order to have unity builds that include their dependencies. For your support library you do + +```meson +my_support_sources = files(...) + +mysupportlib = shared_library( + ... + sources : my_support_sources, + ...) +mysupportlib_dep = declare_dependency( + ... + link_with : mylibrary, + sources : my_support_sources, + ...) +``` + +And for your main project you do: + +```meson +mylibrary = shared_library( + ... + dependencies : mysupportlib_dep, + ...) +myexe = executable( + ... + link_with : mylibrary, + dependencies : mysupportlib_dep, + ...) +``` + +This is extremely dangerous. When building, `mylibrary` will build and link the support sources `my_support_sources` into the resulting shared library. Then, for `myexe`, these same support sources will be compiled again, will be linked into the resulting executable, in addition to them being already present in `mylibrary`. This can quickly run afoul of the [One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule) in C++, as you have more than one definition of a symbol, yielding undefined behavior. While C does not have a strict ODR rule, there is no language in the standard which guarantees such behavior to work. Violations of the ODR can lead to weird idiosyncratic failures such as segfaults. In the overwhelming number of cases, exposing library sources via the `sources` argument in `declare_dependency` is thus incorrect. If you wish to get full cross-library performance, consider building `mysupportlib` as a static library instead and employing LTO. + +There are exceptions to this rule. If there are some natural constraints on how your library is to be used, you can expose sources. For instance, the WrapDB module for GoogleTest directly exposes the sources of GTest and GMock. This is valid, as GTest and GMock will only ever be used in *terminal* link targets. A terminal target is the final target in a dependency link chain, for instance `myexe` in the last example, whereas `mylibrary` is an intermediate link target. For most libraries this rule is not applicable though, as you cannot in general control how others consume your library, and as such should not expose sources. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index f967de0..4dbf41c 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -20,10 +20,11 @@ from .. import mlog from .. import compilers import json import subprocess -from ..mesonlib import MesonException, get_meson_script +from ..mesonlib import MesonException from ..mesonlib import get_compiler_for_source, classify_unity_sources from ..compilers import CompilerArgs from collections import OrderedDict +import shlex class CleanTrees: ''' @@ -771,7 +772,8 @@ class Backend: def run_postconf_scripts(self): env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), 'MESON_BUILD_ROOT': self.environment.get_build_dir(), - 'MESONINTROSPECT': get_meson_script(self.environment, 'mesonintrospect')} + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]), + } child_env = os.environ.copy() child_env.update(env) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 2d2c034..ec811cb 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -25,7 +25,7 @@ from .. import compilers from ..compilers import CompilerArgs from ..linkers import ArLinker from ..mesonlib import File, MesonException, OrderedSet -from ..mesonlib import get_meson_script, get_compiler_for_source +from ..mesonlib import get_compiler_for_source from .backends import CleanTrees, InstallData from ..build import InvalidArguments @@ -520,8 +520,7 @@ int dummy; # All targets are built from the build dir self.environment.get_build_dir(), capture=ofilenames[0] if target.capture else None) - cmd = [sys.executable, self.environment.get_build_command(), - '--internal', 'exe', exe_data] + cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data] cmd_type = 'meson_exe.py custom' else: cmd_type = 'custom' @@ -537,7 +536,7 @@ int dummy; self.processed_targets[target.name + target.type_suffix()] = True def generate_run_target(self, target, outfile): - cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'commandrunner'] + cmd = self.environment.get_build_command() + ['--internal', 'commandrunner'] deps = self.unwrap_dep_list(target) arg_strings = [] for i in target.args: @@ -555,8 +554,7 @@ int dummy; elem = NinjaBuildElement(self.all_outputs, 'meson-' + target.name, 'CUSTOM_COMMAND', []) cmd += [self.environment.get_source_dir(), self.environment.get_build_dir(), - target.subdir, - get_meson_script(self.environment, 'mesonintrospect')] + target.subdir] + self.environment.get_build_command() texe = target.command try: texe = texe.held_object @@ -594,12 +592,11 @@ int dummy; def generate_coverage_rules(self, outfile): e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY') - e.add_item('COMMAND', [sys.executable, - self.environment.get_build_command(), - '--internal', 'coverage', - self.environment.get_source_dir(), - self.environment.get_build_dir(), - self.environment.get_log_dir()]) + e.add_item('COMMAND', self.environment.get_build_command() + + ['--internal', 'coverage', + self.environment.get_source_dir(), + self.environment.get_build_dir(), + self.environment.get_log_dir()]) e.add_item('description', 'Generates coverage reports.') e.write(outfile) # Alias that runs the target defined above @@ -629,7 +626,7 @@ int dummy; added_rule = True htmloutdir = os.path.join(self.environment.get_log_dir(), 'coveragereport') covinfo = os.path.join(self.environment.get_log_dir(), 'coverage.info') - phony_elem = NinjaBuildElement(self.all_outputs, 'coverage-html', 'phony', os.path.join(htmloutdir, 'index.html')) + phony_elem = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'phony', os.path.join(htmloutdir, 'index.html')) phony_elem.write(outfile) # Alias that runs the target defined above self.create_target_alias('meson-coverage-html', outfile) @@ -659,12 +656,11 @@ int dummy; d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), self.environment.get_prefix(), - strip_bin, - get_meson_script(self.environment, 'mesonintrospect')) + strip_bin, self.environment.get_build_command() + ['introspect']) elem = NinjaBuildElement(self.all_outputs, 'meson-install', 'CUSTOM_COMMAND', 'PHONY') elem.add_dep('all') elem.add_item('DESC', 'Installing files.') - elem.add_item('COMMAND', [sys.executable, self.environment.get_build_command(), '--internal', 'install', install_data_file]) + elem.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'install', install_data_file]) elem.add_item('pool', 'console') self.generate_depmf_install(d) self.generate_target_install(d) @@ -844,7 +840,7 @@ int dummy; def generate_tests(self, outfile): self.serialize_tests() - cmd = [sys.executable, '-u', self.environment.get_build_command(), 'test', '--no-rebuild'] + cmd = self.environment.get_build_command(True) + ['test', '--no-rebuild'] if not self.environment.coredata.get_builtin_option('stdsplit'): cmd += ['--no-stdsplit'] if self.environment.coredata.get_builtin_option('errorlogs'): @@ -858,7 +854,7 @@ int dummy; self.create_target_alias('meson-test', outfile) # And then benchmarks. - cmd = [sys.executable, '-u', self.environment.get_build_command(), 'test', '--benchmark', '--logbase', + cmd = self.environment.get_build_command(True) + ['test', '--benchmark', '--logbase', 'benchmarklog', '--num-processes=1', '--no-rebuild'] elem = NinjaBuildElement(self.all_outputs, 'meson-benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY']) elem.add_item('COMMAND', cmd) @@ -896,13 +892,12 @@ int dummy; outfile.write(' depfile = $DEPFILE\n') outfile.write(' restat = 1\n\n') outfile.write('rule REGENERATE_BUILD\n') - c = (ninja_quote(quote_func(sys.executable)), - ninja_quote(quote_func(self.environment.get_build_command())), - '--internal', + c = [ninja_quote(quote_func(x)) for x in self.environment.get_build_command()] + \ + ['--internal', 'regenerate', ninja_quote(quote_func(self.environment.get_source_dir())), - ninja_quote(quote_func(self.environment.get_build_dir()))) - outfile.write(" command = %s %s %s %s %s %s --backend ninja\n" % c) + ninja_quote(quote_func(self.environment.get_build_dir()))] + outfile.write(" command = " + ' '.join(c) + ' --backend ninja\n') outfile.write(' description = Regenerating build files.\n') outfile.write(' generator = 1\n\n') outfile.write('\n') @@ -1500,13 +1495,13 @@ int dummy; outfile.write(description) outfile.write('\n') outfile.write('\n') + args = [ninja_quote(quote_func(x)) for x in self.environment.get_build_command()] + \ + ['--internal', + 'symbolextractor', + '$in', + '$out'] symrule = 'rule SHSYM\n' - symcmd = ' command = "%s" "%s" %s %s %s %s $CROSS\n' % (ninja_quote(sys.executable), - self.environment.get_build_command(), - '--internal', - 'symbolextractor', - '$in', - '$out') + symcmd = ' command = ' + ' '.join(args) + ' $CROSS\n' synstat = ' restat = 1\n' syndesc = ' description = Generating symbol file $out.\n' outfile.write(symrule) @@ -1564,8 +1559,7 @@ int dummy; def generate_swift_compile_rules(self, compiler, outfile): rule = 'rule %s_COMPILER\n' % compiler.get_language() - full_exe = [ninja_quote(sys.executable), - ninja_quote(self.environment.get_build_command()), + full_exe = [ninja_quote(x) for x in self.environment.get_build_command()] + [ '--internal', 'dirchanger', '$RUNDIR'] @@ -2489,9 +2483,7 @@ rule FORTRAN_DEP_HACK e = NinjaBuildElement(self.all_outputs, 'meson-clean-ctlist', 'CUSTOM_COMMAND', 'PHONY') d = CleanTrees(self.environment.get_build_dir(), trees) d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat') - e.add_item('COMMAND', [sys.executable, - self.environment.get_build_command(), - '--internal', 'cleantrees', d_file]) + e.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'cleantrees', d_file]) e.add_item('description', 'Cleaning custom target directories.') e.write(outfile) # Alias that runs the target defined above @@ -2532,13 +2524,10 @@ rule FORTRAN_DEP_HACK def generate_dist(self, outfile): elem = NinjaBuildElement(self.all_outputs, 'meson-dist', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('DESC', 'Creating source packages') - elem.add_item('COMMAND', [sys.executable, - self.environment.get_build_command(), - '--internal', 'dist', - self.environment.source_dir, - self.environment.build_dir, - sys.executable, - self.environment.get_build_command()]) + elem.add_item('COMMAND', self.environment.get_build_command() + + ['--internal', 'dist', + self.environment.source_dir, + self.environment.build_dir] + self.environment.get_build_command()) elem.add_item('pool', 'console') elem.write(outfile) # Alias that runs the target defined above @@ -2546,17 +2535,16 @@ rule FORTRAN_DEP_HACK # For things like scan-build and other helper tools we might have. def generate_utils(self, outfile): - cmd = [sys.executable, self.environment.get_build_command(), - '--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir, - sys.executable, self.environment.get_build_command()] + self.get_user_option_args() + cmd = self.environment.get_build_command() + \ + ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir] + \ + self.environment.get_build_command() + self.get_user_option_args() elem = NinjaBuildElement(self.all_outputs, 'meson-scan-build', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('COMMAND', cmd) elem.add_item('pool', 'console') elem.write(outfile) # Alias that runs the target defined above self.create_target_alias('meson-scan-build', outfile) - cmd = [sys.executable, self.environment.get_build_command(), - '--internal', 'uninstall'] + cmd = self.environment.get_build_command() + ['--internal', 'uninstall'] elem = NinjaBuildElement(self.all_outputs, 'meson-uninstall', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('COMMAND', cmd) elem.add_item('pool', 'console') diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 4832f7e..00ee34a 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -24,7 +24,7 @@ from .. import mlog from .. import compilers from ..build import BuildTarget from ..compilers import CompilerArgs -from ..mesonlib import MesonException, File, get_meson_script +from ..mesonlib import MesonException, File from ..environment import Environment def autodetect_vs_version(build): @@ -413,8 +413,8 @@ class Vs2010Backend(backends.Backend): cmd = [sys.executable, os.path.join(self.environment.get_script_dir(), 'commandrunner.py'), self.environment.get_build_dir(), self.environment.get_source_dir(), - self.get_target_dir(target), - get_meson_script(self.environment, 'mesonintrospect')] + self.get_target_dir(target)] + \ + self.environment.get_build_command() for i in cmd_raw: if isinstance(i, build.BuildTarget): cmd.append(os.path.join(self.environment.get_build_dir(), self.get_target_filename(i))) @@ -447,8 +447,7 @@ class Vs2010Backend(backends.Backend): # All targets run from the target dir tdir_abs, capture=ofilenames[0] if target.capture else None) - wrapper_cmd = [sys.executable, self.environment.get_build_command(), - '--internal', 'exe', exe_data] + wrapper_cmd = self.environment.get_build_command() + ['--internal', 'exe', exe_data] ET.SubElement(customstep, 'Command').text = ' '.join(self.quote_arguments(wrapper_cmd)) ET.SubElement(customstep, 'Outputs').text = ';'.join(ofilenames) ET.SubElement(customstep, 'Inputs').text = ';'.join([exe_data] + srcs + depend_files) @@ -1093,10 +1092,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb' ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c' ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' - regen_command = [sys.executable, - self.environment.get_build_command(), - '--internal', - 'regencheck'] + regen_command = self.environment.get_build_command() + ['--internal', 'regencheck'] private_dir = self.environment.get_scratch_dir() cmd_templ = '''setlocal "%s" "%s" @@ -1176,10 +1172,7 @@ if %%errorlevel%% neq 0 goto :VCEnd''' postbuild = ET.SubElement(action, 'PostBuildEvent') ET.SubElement(postbuild, 'Message') # FIXME: No benchmarks? - test_command = [sys.executable, - self.environment.get_build_command(), - 'test', - '--no-rebuild'] + test_command = self.environment.get_build_command() + ['test', '--no-rebuild'] if not self.environment.coredata.get_builtin_option('stdsplit'): test_command += ['--no-stdsplit'] if self.environment.coredata.get_builtin_option('errorlogs'): diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 3c993ac..5077a6e 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1041,8 +1041,11 @@ class ClangCompiler: return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) def has_multi_arguments(self, args, env): + myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument'] + if mesonlib.version_compare(self.version, '>=3.6.0'): + myargs.append('-Werror=ignored-optimization-argument') return super().has_multi_arguments( - ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument'] + args, + myargs + args, env) def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index e9effc6..0112fd3 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -142,7 +142,7 @@ class BoostDependency(ExternalDependency): def validate_requested(self): for m in self.requested_modules: - if m not in self.src_modules: + if m not in self.src_modules and m not in self.lib_modules and m + '-mt' not in self.lib_modules_mt: msg = 'Requested Boost module {!r} not found' raise DependencyException(msg.format(m)) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index d4af9be..1405c06 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -19,6 +19,7 @@ from .linkers import ArLinker, VisualStudioLinker from . import mesonlib from .mesonlib import EnvironmentException, Popen_safe from . import mlog +import sys from . import compilers from .compilers import ( @@ -361,8 +362,15 @@ class Environment: def get_coredata(self): return self.coredata - def get_build_command(self): - return self.meson_script_launcher + def get_build_command(self, unbuffered=False): + # If running an executable created with cx_freeze, + # Python might not be installed so don't prefix + # the command with it. + if sys.executable.endswith('meson.exe'): + return [sys.executable] + if unbuffered: + [sys.executable, '-u', self.meson_script_launcher] + return [sys.executable, self.meson_script_launcher] def is_header(self, fname): return is_header(fname) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 5fa0878..a845eff 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -21,7 +21,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, Popen_safe, get_meson_script +from .mesonlib import FileMode, Popen_safe from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, DependencyException from .interpreterbase import InterpreterBase @@ -31,7 +31,7 @@ from .interpreterbase import InterpreterObject, MutableInterpreterObject from .modules import ModuleReturnValue import os, sys, shutil, uuid -import re +import re, shlex from collections import namedtuple import importlib @@ -88,7 +88,8 @@ class RunProcess(InterpreterObject): env = {'MESON_SOURCE_ROOT': source_dir, 'MESON_BUILD_ROOT': build_dir, 'MESON_SUBDIR': subdir, - 'MESONINTROSPECT': mesonintrospect} + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]), + } if in_builddir: cwd = os.path.join(build_dir, subdir) else: @@ -97,7 +98,13 @@ class RunProcess(InterpreterObject): child_env.update(env) mlog.debug('Running command:', ' '.join(command_array)) try: - return Popen_safe(command_array, env=child_env, cwd=cwd) + p, o, e = Popen_safe(command_array, env=child_env, cwd=cwd) + mlog.debug('--- stdout----') + mlog.debug(o) + mlog.debug('----stderr----') + mlog.debug(e) + mlog.debug('') + return p, o, e except FileNotFoundError: raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array)) @@ -1269,9 +1276,7 @@ build_target_common_kwargs = ( rust_kwargs | cs_kwargs) -exe_kwargs = set() -exe_kwargs.update(build_target_common_kwargs) - +exe_kwargs = (build_target_common_kwargs) | {'implib'} shlib_kwargs = (build_target_common_kwargs) | {'version', 'soversion'} shmod_kwargs = shlib_kwargs stlib_kwargs = shlib_kwargs @@ -1607,7 +1612,7 @@ class Interpreter(InterpreterBase): else: raise InterpreterException('Arguments ' + m.format(a)) return RunProcess(cmd, expanded_args, srcdir, builddir, self.subdir, - get_meson_script(self.environment, 'mesonintrospect'), in_builddir) + self.environment.get_build_command() + ['introspect'], in_builddir) @stringArgs def func_gettext(self, nodes, args, kwargs): @@ -2190,16 +2195,15 @@ class Interpreter(InterpreterBase): else: vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command... - kwargs['command'] = [sys.executable, - self.environment.get_build_command(), - '--internal', - 'vcstagger', - '@INPUT0@', - '@OUTPUT0@', - fallback, - source_dir, - replace_string, - regex_selector] + vcs_cmd + kwargs['command'] = self.environment.get_build_command() + \ + ['--internal', + 'vcstagger', + '@INPUT0@', + '@OUTPUT0@', + fallback, + source_dir, + replace_string, + regex_selector] + vcs_cmd kwargs.setdefault('build_always', True) return self.func_custom_target(node, [kwargs['output']], kwargs) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index d48625f..9a019c6 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -182,15 +182,6 @@ class File: def relative_name(self): return os.path.join(self.subdir, self.fname) -def get_meson_script(env, script): - ''' - Given the path of `meson.py`/`meson`, get the path of a meson script such - as `mesonintrospect` or `mesontest`. - ''' - meson_py = env.get_build_command() - (base, ext) = os.path.splitext(meson_py) - return os.path.join(os.path.dirname(base), script + ext) - def get_compiler_for_source(compilers, src): for comp in compilers: if comp.can_compile(src): diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 31dd37a..ab14b15 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -136,6 +136,12 @@ If you want to change option values, use meson configure instead.''') def generate(self): env = environment.Environment(self.source_dir, self.build_dir, self.meson_script_launcher, self.options, self.original_cmd_line_args) mlog.initialize(env.get_log_dir()) + try: + self._generate(env) + finally: + mlog.shutdown() + + def _generate(self, env): mlog.debug('Build started at', datetime.datetime.now().isoformat()) mlog.debug('Python binary:', sys.executable) mlog.debug('Python system:', platform.system()) diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index 82ee6ba..659c8f5 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -31,7 +31,9 @@ def initialize(logdir): def shutdown(): global log_file if log_file is not None: - log_file.close() + exception_around_goer = log_file + log_file = None + exception_around_goer.close() class AnsiDecorator: plain_code = "\033[0m" diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 43c4881..0a39664 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -660,7 +660,7 @@ class GnomeModule(ExtensionModule): if kwargs: raise MesonException('Unknown arguments passed: {}'.format(', '.join(kwargs.keys()))) - script = [sys.executable, state.environment.get_build_command()] + script = state.environment.get_build_command() args = ['--internal', 'yelphelper', 'install', @@ -676,20 +676,20 @@ class GnomeModule(ExtensionModule): args.append('--langs=' + '@@'.join(langs)) inscript = build.RunScript(script, args) - potargs = [state.environment.get_build_command(), '--internal', 'yelphelper', 'pot', + potargs = state.environment.get_build_command() + ['--internal', 'yelphelper', 'pot', '--subdir=' + state.subdir, '--id=' + project_id, '--sources=' + source_str] - pottarget = build.RunTarget('help-' + project_id + '-pot', sys.executable, - potargs, [], state.subdir) + pottarget = build.RunTarget('help-' + project_id + '-pot', potargs[0], + potargs[1:], [], state.subdir) - poargs = [state.environment.get_build_command(), '--internal', 'yelphelper', 'update-po', + poargs = state.environment.get_build_command() + ['--internal', 'yelphelper', 'update-po', '--subdir=' + state.subdir, '--id=' + project_id, '--sources=' + source_str, '--langs=' + '@@'.join(langs)] - potarget = build.RunTarget('help-' + project_id + '-update-po', sys.executable, - poargs, [], state.subdir) + potarget = build.RunTarget('help-' + project_id + '-update-po', poargs[0], + poargs[1:], [], state.subdir) rv = [inscript, pottarget, potarget] return ModuleReturnValue(None, rv) @@ -717,7 +717,7 @@ class GnomeModule(ExtensionModule): raise MesonException('You can only specify main_xml or main_sgml, not both.') main_file = main_xml targetname = modulename + '-doc' - command = [sys.executable, state.environment.get_build_command()] + command = state.environment.get_build_command() namespace = kwargs.get('namespace', '') mode = kwargs.get('mode', 'auto') diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 00a4fbf..2af09de 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -72,7 +72,7 @@ class I18nModule(ExtensionModule): datadirs = self._get_data_dirs(state, mesonlib.stringlistify(kwargs.pop('data_dirs', []))) datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None - command = [state.environment.get_build_command(), '--internal', 'msgfmthelper', + command = state.environment.get_build_command() + ['--internal', 'msgfmthelper', '@INPUT@', '@OUTPUT@', file_type, podir] if datadirs: command.append(datadirs) @@ -105,28 +105,28 @@ class I18nModule(ExtensionModule): datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None extra_args = '--extra-args=' + '@@'.join(extra_args) if extra_args else None - potargs = [state.environment.get_build_command(), '--internal', 'gettext', 'pot', pkg_arg] + potargs = state.environment.get_build_command() + ['--internal', 'gettext', 'pot', pkg_arg] if datadirs: potargs.append(datadirs) if extra_args: potargs.append(extra_args) - pottarget = build.RunTarget(packagename + '-pot', sys.executable, potargs, [], state.subdir) + pottarget = build.RunTarget(packagename + '-pot', potargs[0], potargs[1:], [], state.subdir) - gmoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'gen_gmo'] + gmoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'gen_gmo'] if lang_arg: gmoargs.append(lang_arg) - gmotarget = build.RunTarget(packagename + '-gmo', sys.executable, gmoargs, [], state.subdir) + gmotarget = build.RunTarget(packagename + '-gmo', gmoargs[0], gmoargs[1:], [], state.subdir) - updatepoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'update_po', pkg_arg] + updatepoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'update_po', pkg_arg] if lang_arg: updatepoargs.append(lang_arg) if datadirs: updatepoargs.append(datadirs) if extra_args: updatepoargs.append(extra_args) - updatepotarget = build.RunTarget(packagename + '-update-po', sys.executable, updatepoargs, [], state.subdir) + updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs[0], updatepoargs[1:], [], state.subdir) - script = [sys.executable, state.environment.get_build_command()] + script = state.environment.get_build_command() args = ['--internal', 'gettext', 'install', '--subdir=' + state.subdir, '--localedir=' + state.environment.coredata.get_builtin_option('localedir'), diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py index 87e3b8b..f99cddb 100644 --- a/mesonbuild/scripts/commandrunner.py +++ b/mesonbuild/scripts/commandrunner.py @@ -15,13 +15,14 @@ """This program is a wrapper to run external commands. It determines what to run, sets up the environment and executes the command.""" -import sys, os, subprocess, shutil +import sys, os, subprocess, shutil, shlex -def run_command(source_dir, build_dir, subdir, mesonintrospect, command, arguments): +def run_command(source_dir, build_dir, subdir, meson_command, command, arguments): env = {'MESON_SOURCE_ROOT': source_dir, 'MESON_BUILD_ROOT': build_dir, 'MESON_SUBDIR': subdir, - 'MESONINTROSPECT': mesonintrospect} + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in meson_command + ['introspect']]), + } cwd = os.path.join(source_dir, subdir) child_env = os.environ.copy() child_env.update(env) @@ -47,10 +48,16 @@ def run(args): src_dir = args[0] build_dir = args[1] subdir = args[2] - mesonintrospect = args[3] - command = args[4] - arguments = args[5:] - pc = run_command(src_dir, build_dir, subdir, mesonintrospect, command, arguments) + meson_command = args[3] + if 'python' in meson_command: # Hack. + meson_command = [meson_command, args[4]] + command = args[5] + arguments = args[6:] + else: + meson_command = [meson_command] + command = args[4] + arguments = args[5:] + pc = run_command(src_dir, build_dir, subdir, meson_command, command, arguments) pc.wait() return pc.returncode diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 01ea771..b05ffbc 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -13,6 +13,7 @@ # limitations under the License. import sys, pickle, os, shutil, subprocess, gzip, platform, errno +import shlex from glob import glob from . import depfixer from . import destdir_join @@ -247,7 +248,9 @@ def run_install_script(d): 'MESON_BUILD_ROOT': d.build_dir, 'MESON_INSTALL_PREFIX': d.prefix, 'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix, - 'MESONINTROSPECT': d.mesonintrospect} + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]), + } + child_env = os.environ.copy() child_env.update(env) diff --git a/run_unittests.py b/run_unittests.py index b889c3d..1fb1b4b 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1288,6 +1288,9 @@ int main(int argc, char **argv) { targets = mesonbuild.coredata.forbidden_target_names # We don't actually define a target with this name targets.pop('build.ninja') + # Remove this to avoid multiple entries with the same name + # but different case. + targets.pop('PHONY') for i in targets: self.assertPathExists(os.path.join(testdir, i)) @@ -1907,6 +1910,19 @@ class LinuxlikeTests(BasePlatformTests): for i in compdb: self.assertIn("-fsanitize=address", i["command"]) + def test_coverage(self): + if not shutil.which('gcovr'): + raise unittest.SkipTest('gcovr not found') + if not shutil.which('genhtml'): + raise unittest.SkipTest('genhtml not found') + if 'clang' in os.environ.get('CC', '') and os.environ.get('TRAVIS_OS_NAME', '') == 'linux': + raise unittest.SkipTest('Gcovr has a bug and does not work with Clang in the CI environment.') + testdir = os.path.join(self.common_test_dir, '1 trivial') + self.init(testdir, ['-Db_coverage=true']) + self.build() + self.run_tests() + self.run_target('coverage-html') + class LinuxArmCrossCompileTests(BasePlatformTests): ''' Tests that verify cross-compilation to Linux/ARM diff --git a/test cases/common/112 has arg/meson.build b/test cases/common/112 has arg/meson.build index 15d8cc8..a89b59e 100644 --- a/test cases/common/112 has arg/meson.build +++ b/test cases/common/112 has arg/meson.build @@ -42,3 +42,10 @@ if cc.get_id() == 'gcc' assert(cc.has_multi_arguments(pre_arg), 'Arg that should have worked does not work.') assert(cc.has_multi_arguments([pre_arg, arg]), 'Arg that should have worked does not work.') endif + +if cc.get_id() == 'clang' and cc.version().version_compare('<=4.0.0') + # 4.0.0 does not support -fpeel-loops. Newer versions may. + # Please adjust above version number as new versions of clang are released. + notyet_arg = '-fpeel-loops' + assert(not cc.has_argument(notyet_arg), 'Arg that should be broken (unless clang added support recently) is not.') +endif diff --git a/test cases/common/147 mesonintrospect from scripts/check_env.py b/test cases/common/147 mesonintrospect from scripts/check_env.py index dc8ad63..9bd64d7 100644 --- a/test cases/common/147 mesonintrospect from scripts/check_env.py +++ b/test cases/common/147 mesonintrospect from scripts/check_env.py @@ -2,6 +2,7 @@ import os import sys +import shlex do_print = False @@ -13,8 +14,15 @@ if 'MESONINTROSPECT' not in os.environ: mesonintrospect = os.environ['MESONINTROSPECT'] -if not os.path.isfile(mesonintrospect): +introspect_arr = shlex.split(mesonintrospect) + +#print(mesonintrospect) +#print(introspect_arr) + +some_executable = introspect_arr[0] + +if not os.path.isfile(some_executable): raise RuntimeError('{!r} does not exist'.format(mesonintrospect)) if do_print: - print(mesonintrospect, end='') + print(some_executable, end='') diff --git a/test cases/common/159 reserved targets/PHONY/meson.build b/test cases/common/159 reserved targets/PHONY/meson.build deleted file mode 100644 index d25583c..0000000 --- a/test cases/common/159 reserved targets/PHONY/meson.build +++ /dev/null @@ -1 +0,0 @@ -executable('test-PHONY', '../test.c') diff --git a/test cases/common/159 reserved targets/meson.build b/test cases/common/159 reserved targets/meson.build index 5123600..26572c0 100644 --- a/test cases/common/159 reserved targets/meson.build +++ b/test cases/common/159 reserved targets/meson.build @@ -15,11 +15,10 @@ subdir('coverage-xml') subdir('dist') subdir('distcheck') subdir('install') -# We end up creating duplicate lowercase target names for this on -# case-insensitive HFS+, so disable it -# https://travis-ci.org/mesonbuild/meson/jobs/264468097 -#subdir('phony') -subdir('PHONY') +# We don't have a 'PHONY' directory because Windows and OSX +# choke horribly when there are two entries with the same +# name but different case. +subdir('phony') subdir('reconfigure') subdir('scan-build') subdir('test') diff --git a/test cases/frameworks/1 boost/extralib.cpp b/test cases/frameworks/1 boost/extralib.cpp new file mode 100644 index 0000000..6a3e9e4 --- /dev/null +++ b/test cases/frameworks/1 boost/extralib.cpp @@ -0,0 +1,25 @@ +#include <iostream> +#include <boost/log/trivial.hpp> +#include <boost/log/expressions.hpp> +#include <boost/log/utility/setup/console.hpp> +#include <boost/log/utility/setup/common_attributes.hpp> + +using namespace std; +namespace logging = boost::log; + +void InitLogger() { + logging::add_common_attributes(); + logging::register_simple_formatter_factory<logging::trivial::severity_level, char>("Severity"); + string log_format = "%TimeStamp% [%Severity%] - %Message%"; + + logging::add_console_log( + cout, + logging::keywords::format = log_format + ); +} + +int main(int argc, char **argv) { + InitLogger(); + BOOST_LOG_TRIVIAL(trace) << "SOMETHING"; + return 0; +} diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build index 338dd78..6f25f8b 100644 --- a/test cases/frameworks/1 boost/meson.build +++ b/test cases/frameworks/1 boost/meson.build @@ -1,6 +1,10 @@ project('boosttest', 'cpp', default_options : ['cpp_std=c++11']) +add_project_arguments(['-DBOOST_LOG_DYN_LINK'], + language : 'cpp' +) + # We want to have multiple separate configurations of Boost # within one project. The need to be independent of each other. # Use one without a library dependency and one with it. @@ -10,15 +14,18 @@ linkdep = dependency('boost', modules : ['thread', 'system']) staticdep = dependency('boost', modules : ['thread', 'system'], static : true) testdep = dependency('boost', modules : 'test') nomoddep = dependency('boost') +extralibdep = dependency('boost', modules : ['thread', 'system', 'log_setup', 'log']) nolinkexe = executable('nolinkedexe', 'nolinkexe.cc', dependencies : nolinkdep) linkexe = executable('linkedexe', 'linkexe.cc', dependencies : linkdep) staticexe = executable('staticlinkedexe', 'linkexe.cc', dependencies : staticdep) unitexe = executable('utf', 'unit_test.cpp', dependencies: testdep) nomodexe = executable('nomod', 'nomod.cpp', dependencies : nomoddep) +extralibexe = executable('extralibexe', 'extralib.cpp', dependencies : extralibdep) test('Boost nolinktest', nolinkexe) test('Boost linktest', linkexe) test('Boost statictest', staticexe) test('Boost UTF test', unitexe) test('Boost nomod', nomodexe) +test('Boost extralib test', extralibexe) |