diff options
52 files changed, 937 insertions, 185 deletions
diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md index cb7b19e..cd7f07d 100644 --- a/docs/markdown/Build-options.md +++ b/docs/markdown/Build-options.md @@ -16,18 +16,43 @@ Here is a simple option file. option('someoption', type : 'string', value : 'optval', description : 'An option') option('other_one', type : 'boolean', value : false) option('combo_opt', type : 'combo', choices : ['one', 'two', 'three'], value : 'three') +option('free_array_opt', type : 'array', value : ['one', 'two']) +option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two']) ``` -This demonstrates the three basic option types and their usage. String -option is just a free form string and a boolean option is, -unsurprisingly, true or false. The combo option can have any value -from the strings listed in argument `choices`. If `value` is not set, -it defaults to empty string for strings, `true` for booleans or the -first element in a combo. You can specify `description`, which is a -free form piece of text describing the option. It defaults to option -name. +All types allow a `description` value to be set describing the option, +if no option is set then the name of the option will be used instead. -These options are accessed in Meson code with the `get_option` function. +### Strings + +The string type is a free form string. If the default value is not set +then an empty string will be used as the default. + +### Booleans + +Booleans may have values of either `true` or `false`. If no default +value is supplied then `true` will be used as the default. + +### Combos + +A combo allows any one of the values in the `choices` parameter to be +selected. If no default value is set then the first value will be the +default. + +### Arrays + +Arrays represent an array of strings. By default the array can contain +arbitrary strings. To limit the possible values that can used set the +`choices` parameter. Meson will then only allow the value array to +contain strings that are in the given list. The array may be +empty. The `value` parameter specifies the default value of the option +and if it is unset then the values of `choices` will be used as the +default. + +This type is new in version 0.44.0 + + +## Using build options ```meson optval = get_option('opt_name') @@ -42,9 +67,9 @@ prefix = get_option('prefix') ``` It should be noted that you can not set option values in your Meson -scripts. They have to be set externally with the `meson configure` command -line tool. Running `meson configure` without arguments in a build dir shows -you all options you can set. +scripts. They have to be set externally with the `meson configure` +command line tool. Running `meson configure` without arguments in a +build dir shows you all options you can set. To change their values use the `-D` option: @@ -53,5 +78,26 @@ option: $ meson configure -Doption=newvalue ``` +Setting the value of arrays is a bit special. If you only pass a +single string, then it is considered to have all values separated by +commas. Thus invoking the following command: + +```console +$ meson configure -Darray_opt=foo,bar +``` + +would set the value to an array of two elements, `foo` and `bar`. + +If you need to have commas in your string values, then you need to +pass the value with proper shell quoting like this: + +```console +$ meson configure "-Doption=['a,b', 'c,d']" +``` + +The inner values must always be single quotes and the outer ones +double quotes. -**NOTE:** If you cannot call `meson configure` you likely have a old version of Meson. In that case you can call `mesonconf` instead, but that is deprecated in newer versions +**NOTE:** If you cannot call `meson configure` you likely have a old + version of Meson. In that case you can call `mesonconf` instead, but + that is deprecated in newer versions diff --git a/docs/markdown/Disabler.md b/docs/markdown/Disabler.md new file mode 100644 index 0000000..2d50c5c --- /dev/null +++ b/docs/markdown/Disabler.md @@ -0,0 +1,65 @@ +--- +short-description: Disabling options +... + +# Disabling parts of the build (available since 0.44.0) + +The following is a common fragment found in many projects: + +```meson +dep = dependency('foo') + +# In some different directory + +lib = shared_library('mylib', 'mylib.c', + dependencies : dep) + +# And Ãn a third directory + +exe = executable('mytest', 'mytest.c', + link_with : lib) +test('mytest', exe) +``` + +This works fine but gets a bit inflexible when you want to make this +part of the build optional. Basically it reduces to adding `if/else` +statements around all target invocations. Meson provides a simpler way +of achieving the same with a disabler object. + +A disabler object is created with the `disabler` function: + +```meson +d = disabler() +``` + +The only thing you can do to a disabler object is to ask if it has +been found: + +```meson +f = d.found() # returns false +``` + +Any other statement that uses a disabler object will immediately +return a disabler. For example assuming that `d` contains a disabler +object then + +```meson +d2 = some_func(d) # value of d2 will be disabler +d3 = true or d2 # value of d3 will be disabler +if d # neither branch is evaluated +``` + +Thus to disable every target that depends on the dependency given +above, you can do something like this: + +```meson +if use_foo_feature + d = dependency('foo') +else + d = disabler() +endif +``` + +This concentrates the handling of this option in one place and other +build definition files do not need to be sprinkled with `if` +statements.
\ No newline at end of file diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index e6aa9d3..13a2b2a 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -316,9 +316,20 @@ otherwise. This function supports the following keyword arguments: You can also specify multiple restrictions by passing a list to this keyword argument, such as: `['>=3.14.0', '<=4.1.0']`. +If dependency_name is '', the dependency is always not found. So with +`required: false`, this always returns a dependency object for which the +`found()` method returns `false`, and which can be passed like any other +dependency to the `dependencies:` keyword argument of a `build_target`. This +can be used to implement a dependency which is sometimes not required e.g. in +some branches of a conditional. + The returned object also has methods that are documented in the [object methods section](#dependency-object) below. +### disabler() + +Returns a [disabler object]((#disabler-object)). Added in 0.44.0. + ### error() ``` meson @@ -436,10 +447,7 @@ be passed to [shared and static libraries](#library). The list of `sources`, `objects`, and `dependencies` is always flattened, which means you can freely nest and add lists while -creating the final list. As a corollary, the best way to handle a -'disabled dependency' is by assigning an empty list `[]` to it and -passing it like any other dependency to the `dependencies:` keyword -argument. +creating the final list. The returned object also has methods that are documented in the [object methods section](#build-target-object) below. @@ -759,11 +767,7 @@ installed with a `.gz` suffix. Installs the entire given subdirectory and its contents from the source tree to the location specified by the keyword argument -`install_dir`. Note that due to implementation issues this command -deletes the entire target dir before copying the files, so you should -never use `install_subdir` to install into two overlapping directories -(such as `foo` and `foo/bar`) because if you do the behavior is -undefined. +`install_dir`. The following keyword arguments are supported: @@ -1627,6 +1631,15 @@ an external dependency with the following methods: - `version()` is the version number as a string, for example `1.2.8` +### `disabler` object + +A disabler object is an object that behaves in much the same way as +NaN numbers do in floating point math. That is when used in any +statement (function call, logical op, etc) they will cause the +statement evaluation to immediately short circuit to return a disabler +object. A disabler object has one method: + + - `found()`, always returns `false` ### `external program` object diff --git a/docs/markdown/Running-Meson.md b/docs/markdown/Running-Meson.md index 0e8da43..23d5e97 100644 --- a/docs/markdown/Running-Meson.md +++ b/docs/markdown/Running-Meson.md @@ -1,17 +1,26 @@ --- -short-description: Building a project with meson +short-description: Building a project with Meson ... -# Running meson +# Running Meson -There are two different ways of invoking Meson. First, you can run it directly from the source tree with the command `/path/to/source/meson.py`. Meson may also be installed in which case the command is simply `meson`. In this manual we only use the latter format for simplicity. +There are two different ways of invoking Meson. First, you can run it +directly from the source tree with the command +`/path/to/source/meson.py`. Meson may also be installed in which case +the command is simply `meson`. In this manual we only use the latter +format for simplicity. -At the time of writing only a command line version of Meson is available. This means that Meson must be invoked using the terminal. If you wish to use the MSVC compiler, you need to run Meson under "Visual Studio command prompt". +At the time of writing only a command line version of Meson is +available. This means that Meson must be invoked using the +terminal. If you wish to use the MSVC compiler, you need to run Meson +under "Visual Studio command prompt". Configuring the source == -Let us assume that we have a source tree that has a Meson build system. This means that at the topmost directory has a file called `meson.build`. We run the following commands to get the build started. +Let us assume that we have a source tree that has a Meson build +system. This means that at the topmost directory has a file called +`meson.build`. We run the following commands to get the build started. cd /path/to/source/root @@ -19,13 +28,22 @@ Let us assume that we have a source tree that has a Meson build system. This mea cd builddir meson .. -First we create a directory to hold all files generated during the build. Then we go into it and invoke Meson, giving it the location of the source root. +First we create a directory to hold all files generated during the +build. Then we go into it and invoke Meson, giving it the location of +the source root. -Hint: The syntax of meson is `meson [options] [srcdir] [builddir]`, but you may omit either `srcdir` or `builddir`. Meson will deduce the `srcdir` by the location of `meson.build`. The other one will be your `pwd`. +Hint: The syntax of meson is `meson [options] [srcdir] [builddir]`, +but you may omit either `srcdir` or `builddir`. Meson will deduce the +`srcdir` by the location of `meson.build`. The other one will be your +`pwd`. -Meson then loads the build configuration file and writes the corresponding build backend in the build directory. By default Meson generates a *debug build*, which turns on basic warnings and debug information and disables compiler optimizations. +Meson then loads the build configuration file and writes the +corresponding build backend in the build directory. By default Meson +generates a *debug build*, which turns on basic warnings and debug +information and disables compiler optimizations. -You can specify a different type of build with the `--buildtype` command line argument. It can have one of the following values. +You can specify a different type of build with the `--buildtype` +command line argument. It can have one of the following values. | value | meaning | | ------ | -------- | @@ -34,42 +52,78 @@ You can specify a different type of build with the `--buildtype` command line ar | `debugoptimized` | debug info is generated and the code is optimized (on most compilers this means `-g -O2`) | | `release` | full optimization, no debug info | -The build directory is mandatory. The reason for this is that it simplifies the build process immensely. Meson will not under any circumstances write files inside the source directory (if it does, it is a bug and should be fixed). This means that the user does not need to add a bunch of files to their revision control's ignore list. It also means that you can create arbitrarily many build directories for any given source tree. If we wanted to test building the source code with the Clang compiler instead of the system default, we could just type the following commands. +The build directory is mandatory. The reason for this is that it +simplifies the build process immensely. Meson will not under any +circumstances write files inside the source directory (if it does, it +is a bug and should be fixed). This means that the user does not need +to add a bunch of files to their revision control's ignore list. It +also means that you can create arbitrarily many build directories for +any given source tree. If we wanted to test building the source code +with the Clang compiler instead of the system default, we could just +type the following commands. cd /path/to/source/root mkdir buildclang cd buildclang CC=clang CXX=clang++ meson .. -This separation is even more powerful if your code has multiple configuration options (such as multiple data backends). You can create a separate subdirectory for each of them. You can also have build directories for optimized builds, code coverage, static analysis and so on. They are all neatly separated and use the same source tree. Changing between different configurations is just a question of changing to the corresponding directory. +This separation is even more powerful if your code has multiple +configuration options (such as multiple data backends). You can create +a separate subdirectory for each of them. You can also have build +directories for optimized builds, code coverage, static analysis and +so on. They are all neatly separated and use the same source +tree. Changing between different configurations is just a question of +changing to the corresponding directory. -Unless otherwise mentioned, all following command line invocations are meant to be run in the build directory. +Unless otherwise mentioned, all following command line invocations are +meant to be run in the build directory. -By default Meson will use the Ninja backend to build your project. If you wish to use any of the other backends, you need to pass the corresponding argument during configuration time. As an example, here is how you would use Meson to generate a Visual studio solution. +By default Meson will use the Ninja backend to build your project. If +you wish to use any of the other backends, you need to pass the +corresponding argument during configuration time. As an example, here +is how you would use Meson to generate a Visual studio solution. meson <source dir> <build dir> --backend=vs2010 -You can then open the generated solution with Visual Studio and compile it in the usual way. A list of backends can be obtained with `meson --help`. +You can then open the generated solution with Visual Studio and +compile it in the usual way. A list of backends can be obtained with +`meson --help`. Building the source == -If you are not using an IDE, Meson uses the [Ninja build system](https://ninja-build.org/) to actually build the code. To start the build, simply type the following command. +If you are not using an IDE, Meson uses the [Ninja build +system](https://ninja-build.org/) to actually build the code. To start +the build, simply type the following command. ninja -The main usability difference between Ninja and Make is that Ninja will automatically detect the number of CPUs in your computer and parallelize itself accordingly. You can override the amount of parallel processes used with the command line argument `-j <num processes>`. - -It should be noted that after the initial configure step `ninja` is the only command you ever need to type to compile. No matter how you alter your source tree (short of moving it to a completely new location), Meson will detect the changes and regenerate itself accordingly. This is especially handy if you have multiple build directories. Often one of them is used for development (the "debug" build) and others only every now and then (such as a "static analysis" build). Any configuration can be built just by `cd`'ing to the corresponding directory and running Ninja. +The main usability difference between Ninja and Make is that Ninja +will automatically detect the number of CPUs in your computer and +parallelize itself accordingly. You can override the amount of +parallel processes used with the command line argument `-j <num +processes>`. + +It should be noted that after the initial configure step `ninja` is +the only command you ever need to type to compile. No matter how you +alter your source tree (short of moving it to a completely new +location), Meson will detect the changes and regenerate itself +accordingly. This is especially handy if you have multiple build +directories. Often one of them is used for development (the "debug" +build) and others only every now and then (such as a "static analysis" +build). Any configuration can be built just by `cd`'ing to the +corresponding directory and running Ninja. Running tests == -Meson provides native support for running tests. The command to do that is simple. +Meson provides native support for running tests. The command to do +that is simple. ninja test -Meson does not force the use of any particular testing framework. You are free to use GTest, Boost Test, Check or even custom executables. +Meson does not force the use of any particular testing framework. You +are free to use GTest, Boost Test, Check or even custom executables. Installing == @@ -78,13 +132,17 @@ Installing the built software is just as simple. ninja install -By default Meson installs to `/usr/local`. This can be changed by passing the command line argument `--prefix /your/prefix` to Meson during configure time. Meson also supports the `DESTDIR` variable used in e.g. building packages. It is used like this: +By default Meson installs to `/usr/local`. This can be changed by +passing the command line argument `--prefix /your/prefix` to Meson +during configure time. Meson also supports the `DESTDIR` variable used +in e.g. building packages. It is used like this: DESTDIR=/path/to/staging ninja install Command line help == -Meson has a standard command line help feature. It can be accessed with the following command. +Meson has a standard command line help feature. It can be accessed +with the following command. meson --help diff --git a/docs/markdown/snippets/disabler.md b/docs/markdown/snippets/disabler.md new file mode 100644 index 0000000..1323048 --- /dev/null +++ b/docs/markdown/snippets/disabler.md @@ -0,0 +1,33 @@ +# Added disabler object + +A disabler object is a new kind of object that has very specific +semantics. If it is used as part of any other operation such as an +argument to a function call, logical operations etc, it will cause the +operation to not be evaluated. Instead the return value of said +operation will also be the disabler object. + +For example if you have an setup like this: + +```meson +dep = dependency('foo') +lib = shared_library('mylib', 'mylib.c', + dependencies : dep) +exe = executable('mytest', 'mytest.c', + link_with : lib) +test('mytest', exe) +``` + +If you replace the dependency with a disabler object like this: + +```meson +dep = disabler() +lib = shared_library('mylib', 'mylib.c', + dependencies : dep) +exe = executable('mytest', 'mytest.c', + link_with : lib) +test('mytest', exe) +``` + +Then the shared library, executable and unit test are not +created. This is a handy mechanism to cut down on the number of `if` +statements. diff --git a/docs/markdown/snippets/get_unquoted.md b/docs/markdown/snippets/get_unquoted.md index 5f8969d..a6bfc4f 100755 --- a/docs/markdown/snippets/get_unquoted.md +++ b/docs/markdown/snippets/get_unquoted.md @@ -1,4 +1,4 @@ -# `get_unquoted()` mehod for the `configuration` data object +# `get_unquoted()` method for the `configuration` data object -New convenience method that allow reusing a variable value +New convenience method that allows reusing a variable value defined quoted. Useful in C for config.h strings for example. diff --git a/docs/markdown/snippets/option-array-type.md b/docs/markdown/snippets/option-array-type.md new file mode 100644 index 0000000..9eb1312 --- /dev/null +++ b/docs/markdown/snippets/option-array-type.md @@ -0,0 +1,18 @@ +# An array type for user options + +Previously to have an option that took more than one value a string value would +have to be created and split, but validating this was difficult. A new array type +has been added to the meson_options.txt for this case. It works like a 'combo', but +allows more than one option to be passed. The values can optionally be validated +against a list of valid values. When used on the command line (with -D), values +are passed as a comma separated list. + +```meson +option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one']) +``` + +These can be overwritten on the command line, + +```meson +meson _build -Darray_opt=two,three +``` diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 6b155af..9c86d60 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -26,6 +26,7 @@ index.md Localisation.md Build-options.md Subprojects.md + Disabler.md Modules.md Gnome-module.md i18n-module.md diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 3e12c89..f7b56bd 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -332,6 +332,8 @@ class Backend: link_deps = target.get_all_link_deps() result = [] for ld in link_deps: + if ld is target: + continue prospective = self.get_target_dir(ld) if prospective not in result: result.append(prospective) @@ -744,9 +746,9 @@ class Backend: deps.append(i.rel_to_builddir(self.build_to_src)) else: if absolute_paths: - deps.append(os.path.join(self.environment.get_build_dir(), i)) + deps.append(os.path.join(self.environment.get_source_dir(), target.subdir, i)) else: - deps.append(os.path.join(self.build_to_src, i)) + deps.append(os.path.join(self.build_to_src, target.subdir, i)) return deps def eval_custom_target_command(self, target, absolute_outputs=False): diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 9e85712..0f92e78 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -18,6 +18,7 @@ from .. import mlog from .. import coredata from ..mesonlib import EnvironmentException, version_compare, Popen_safe, listify from ..mesonlib import for_windows, for_darwin, for_cygwin +from . import compilers from .compilers import ( GCC_MINGW, @@ -89,6 +90,8 @@ class CCompiler(Compiler): # The default behavior is this, override in MSVC def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): + if self.id == 'clang' and self.clang_type == compilers.CLANG_OSX: + return self.build_osx_rpath_args(build_dir, rpath_paths, build_rpath) return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath) def get_dependency_gen_args(self, outtarget, outfile): @@ -724,10 +727,12 @@ class CCompiler(Compiler): if for_darwin(env.is_cross_build(), env): shlibext = ['dylib'] elif for_windows(env.is_cross_build(), env): + # FIXME: .lib files can be import or static so we should read the + # file, figure out which one it is, and reject the wrong kind. if self.id == 'msvc': shlibext = ['lib'] else: - shlibext = ['dll', 'dll.a', 'lib'] + shlibext = ['dll.a', 'lib', 'dll'] # Yep, static libraries can also be foo.lib stlibext += ['lib'] elif for_cygwin(env.is_cross_build(), env): @@ -811,6 +816,12 @@ class ClangCCompiler(ClangCompiler, CCompiler): def get_option_link_args(self, options): return [] + def get_linker_always_args(self): + basic = super().get_linker_always_args() + if self.clang_type == compilers.CLANG_OSX: + return basic + ['-Wl,-headerpad_max_install_names'] + return basic + class GnuCCompiler(GnuCompiler, CCompiler): def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None): @@ -828,8 +839,8 @@ class GnuCCompiler(GnuCompiler, CCompiler): 'none')} if self.gcc_type == GCC_MINGW: opts.update({ - 'c_winlibs': coredata.UserStringArrayOption('c_winlibs', 'Standard Win libraries to link against', - gnu_winlibs), }) + 'c_winlibs': coredata.UserArrayOption('c_winlibs', 'Standard Win libraries to link against', + gnu_winlibs), }) return opts def get_option_compile_args(self, options): @@ -1001,9 +1012,9 @@ class VisualStudioCCompiler(CCompiler): return [] def get_options(self): - return {'c_winlibs': coredata.UserStringArrayOption('c_winlibs', - 'Windows libs to link against.', - msvc_winlibs) + return {'c_winlibs': coredata.UserArrayOption('c_winlibs', + 'Windows libs to link against.', + msvc_winlibs) } def get_option_link_args(self, options): diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 7087b3e..011c222 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -817,6 +817,16 @@ class Compiler: def get_instruction_set_args(self, instruction_set): return None + def build_osx_rpath_args(self, build_dir, rpath_paths, build_rpath): + if not rpath_paths and not build_rpath: + return [] + # On OSX, rpaths must be absolute. + abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths] + if build_rpath != '': + abs_rpaths.append(build_rpath) + args = ['-Wl,-rpath,' + rp for rp in abs_rpaths] + return args + def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): if not rpath_paths and not install_rpath and not build_rpath: return [] @@ -879,7 +889,11 @@ def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, i elif gcc_type == GCC_OSX: if is_shared_module: return [] - return ['-install_name', os.path.join(path, 'lib' + shlib_name + '.dylib')] + install_name = prefix + shlib_name + if soversion is not None: + install_name += '.' + soversion + install_name += '.dylib' + return ['-install_name', os.path.join('@rpath', install_name)] else: raise RuntimeError('Not implemented yet.') diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 15eb9a0..cb4b055 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -76,8 +76,8 @@ class ClangCPPCompiler(ClangCompiler, CPPCompiler): def get_options(self): return {'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use', - ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++1z', - 'gnu++11', 'gnu++14', 'gnu++1z'], + ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z'], 'none')} def get_option_compile_args(self, options): @@ -102,15 +102,15 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): def get_options(self): opts = {'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use', - ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++1z', - 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++1z'], + ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z'], 'none'), 'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl', 'STL debug mode', False)} if self.gcc_type == GCC_MINGW: opts.update({ - 'cpp_winlibs': coredata.UserStringArrayOption('cpp_winlibs', 'Standard Win libraries to link against', + 'cpp_winlibs': coredata.UserArrayOption('cpp_winlibs', 'Standard Win libraries to link against', gnu_winlibs), }) return opts @@ -188,9 +188,9 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): 'C++ exception handling type.', ['none', 'a', 's', 'sc'], 'sc'), - 'cpp_winlibs': coredata.UserStringArrayOption('cpp_winlibs', - 'Windows libs to link against.', - msvc_winlibs) + 'cpp_winlibs': coredata.UserArrayOption('cpp_winlibs', + 'Windows libs to link against.', + msvc_winlibs) } def get_option_compile_args(self, options): diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index 9da9b81..b91da6d 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -94,3 +94,9 @@ class ValaCompiler(Compiler): return [vapi] mlog.debug('Searched {!r} and {!r} wasn\'t found'.format(extra_dirs, libname)) return None + + def thread_flags(self): + return [] + + def thread_link_flags(self): + return [] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 68b1abf..70a2d73 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -125,27 +125,43 @@ class UserComboOption(UserOption): raise MesonException('Value %s not one of accepted values.' % value) return value -class UserStringArrayOption(UserOption): +class UserArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): super().__init__(name, description, kwargs.get('choices', [])) - self.set_value(value) - - def validate(self, value): - if isinstance(value, str): - if not value.startswith('['): - raise MesonException('Valuestring does not define an array: ' + value) - newvalue = ast.literal_eval(value) + self.set_value(value, user_input=False) + + def validate(self, value, user_input): + # User input is for options defined on the command line (via -D + # options). Users can put their input in as a comma separated + # string, but for defining options in meson_options.txt the format + # should match that of a combo + if not user_input: + if isinstance(value, str): + if not value.startswith('['): + raise MesonException('Valuestring does not define an array: ' + value) + newvalue = ast.literal_eval(value) + else: + newvalue = value else: - newvalue = value + assert isinstance(value, str) + if value.startswith('['): + newvalue = ast.literal_eval(value) + else: + newvalue = [v.strip() for v in value.split(',')] if not isinstance(newvalue, list): raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue))) for i in newvalue: if not isinstance(i, str): raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue))) + if self.choices: + bad = [x for x in newvalue if x not in self.choices] + if bad: + raise MesonException('Options "{}" are not in allowed choices: "{}"'.format( + ', '.join(bad), ', '.join(self.choices))) return newvalue - def set_value(self, newvalue): - self.value = self.validate(newvalue) + def set_value(self, newvalue, user_input=True): + self.value = self.validate(newvalue, user_input) def validate_value(self, value): self.validate(value) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index a720232..c2922f0 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -22,6 +22,7 @@ import shlex import shutil import textwrap from enum import Enum +from pathlib import PurePath from .. import mlog from .. import mesonlib @@ -129,11 +130,11 @@ class Dependency: def need_threads(self): return False - def get_pkgconfig_variable(self, variable_name): - raise NotImplementedError('{!r} is not a pkgconfig dependency'.format(self.name)) + def get_pkgconfig_variable(self, variable_name, kwargs): + raise DependencyException('{!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)) + raise DependencyException('{!r} is not a config-tool dependency'.format(self.name)) class InternalDependency(Dependency): @@ -148,6 +149,14 @@ class InternalDependency(Dependency): self.sources = sources self.ext_deps = ext_deps + def get_pkgconfig_variable(self, variable_name, kwargs): + raise DependencyException('Method "get_pkgconfig_variable()" is ' + 'invalid for an internal dependency') + + def get_configtool_variable(self, variable_name): + raise DependencyException('Method "get_configtool_variable()" is ' + 'invalid for an internal dependency') + class ExternalDependency(Dependency): def __init__(self, type_name, environment, language, kwargs): @@ -156,9 +165,6 @@ class ExternalDependency(Dependency): self.name = type_name # default self.is_found = False self.language = language - if language and language not in self.env.coredata.compilers: - m = self.name.capitalize() + ' requires a {} compiler' - raise DependencyException(m.format(language.capitalize())) self.version_reqs = kwargs.get('version', None) self.required = kwargs.get('required', True) self.silent = kwargs.get('silent', False) @@ -176,7 +182,20 @@ class ExternalDependency(Dependency): compilers = self.env.coredata.cross_compilers else: compilers = self.env.coredata.compilers - self.compiler = compilers.get(self.language or 'c', None) + # Set the compiler for this dependency if a language is specified, + # else try to pick something that looks usable. + if self.language: + if self.language not in compilers: + m = self.name.capitalize() + ' requires a {} compiler' + raise DependencyException(m.format(self.language.capitalize())) + self.compiler = compilers[self.language] + else: + # Try to find a compiler that this dependency can use for compiler + # checks. It's ok if we don't find one. + for lang in ('c', 'cpp', 'objc', 'objcpp', 'fortran', 'd'): + self.compiler = compilers.get(lang, None) + if self.compiler: + break def get_compiler(self): return self.compiler @@ -308,8 +327,8 @@ class PkgConfigDependency(ExternalDependency): # multiple times in the same Meson invocation. class_pkgbin = None - def __init__(self, name, environment, kwargs): - super().__init__('pkgconfig', environment, None, kwargs) + def __init__(self, name, environment, kwargs, language=None): + super().__init__('pkgconfig', environment, language, kwargs) self.name = name self.is_libtool = False # Store a copy of the pkg-config path on the object itself so it is @@ -401,12 +420,40 @@ class PkgConfigDependency(ExternalDependency): p, out = Popen_safe([self.pkgbin] + args, env=env)[0:2] return p.returncode, out.strip() + def _convert_mingw_paths(self, args): + ''' + Both MSVC and native Python on Windows cannot handle MinGW-esque /c/foo + paths so convert them to C:/foo. We cannot resolve other paths starting + with / like /home/foo so leave them as-is so that the user gets an + error/warning from the compiler/linker. + ''' + if not mesonlib.is_windows(): + return args + converted = [] + for arg in args: + pargs = [] + # Library search path + if arg.startswith('-L/'): + pargs = PurePath(arg[2:]).parts + tmpl = '-L{}:/{}' + elif arg.startswith('-I/'): + pargs = PurePath(arg[2:]).parts + tmpl = '-I{}:/{}' + # Full path to library or .la file + elif arg.startswith('/'): + pargs = PurePath(arg).parts + tmpl = '{}:/{}' + if len(pargs) > 1 and len(pargs[1]) == 1: + arg = tmpl.format(pargs[1], '/'.join(pargs[2:])) + converted.append(arg) + return converted + def _set_cargs(self): ret, out = self._call_pkgbin(['--cflags', self.name]) if ret != 0: raise DependencyException('Could not generate cargs for %s:\n\n%s' % (self.name, out)) - self.compile_args = shlex.split(out) + self.compile_args = self._convert_mingw_paths(shlex.split(out)) def _set_libs(self): env = None @@ -423,7 +470,7 @@ class PkgConfigDependency(ExternalDependency): (self.name, out)) self.link_args = [] libpaths = [] - for lib in shlex.split(out): + for lib in self._convert_mingw_paths(shlex.split(out)): # If we want to use only static libraries, we have to look for the # file ourselves instead of depending on the compiler to find it # with -lfoo or foo.lib. However, we can only do this if we already @@ -452,8 +499,20 @@ class PkgConfigDependency(ExternalDependency): self.is_libtool = True self.link_args.append(lib) - def get_pkgconfig_variable(self, variable_name): - ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name]) + def get_pkgconfig_variable(self, variable_name, kwargs): + options = ['--variable=' + variable_name, self.name] + + if 'define_variable' in kwargs: + definition = kwargs.get('define_variable', []) + if not isinstance(definition, list): + raise MesonException('define_variable takes a list') + + if len(definition) != 2 or not all(isinstance(i, str) for i in definition): + raise MesonException('define_variable must be made up of 2 strings for VARIABLENAME and VARIABLEVALUE') + + options = ['--define-variable=' + '='.join(definition)] + options + + ret, out = self._call_pkgbin(options) variable = '' if ret != 0: if self.required: diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index f8626e7..ce9313e 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -239,17 +239,18 @@ class BoostDependency(ExternalDependency): def detect_lib_modules_win(self): arch = detect_cpu_family(self.env.coredata.compilers) - compiler_ts = self.env.detect_cpp_compiler(self.want_cross).get_toolset_version().split('.') + comp_ts_version = self.env.detect_cpp_compiler(self.want_cross).get_toolset_version() + compiler_ts = comp_ts_version.split('.') compiler = 'vc{}{}'.format(compiler_ts[0], compiler_ts[1]) if not self.libdir: - # The libdirs in the distributed binaries + # The libdirs in the distributed binaries (from sf) if arch == 'x86': - gl = 'lib32*' + lib_sf = 'lib32-msvc-{}'.format(comp_ts_version) elif arch == 'x86_64': - gl = 'lib64*' + lib_sf = 'lib64-msvc-{}'.format(comp_ts_version) else: # Does anyone do Boost cross-compiling to other archs on Windows? - gl = None + lib_sf = None if self.boost_root: roots = [self.boost_root] else: @@ -260,11 +261,10 @@ class BoostDependency(ExternalDependency): if os.path.isdir(libdir): self.libdir = libdir break - if gl: - tmp = glob.glob(os.path.join(root, gl)) - if len(tmp) > 0: - # FIXME: Should pick the correct version - self.libdir = tmp[0] + if lib_sf: + full_path = os.path.join(root, lib_sf) + if os.path.isdir(full_path): + self.libdir = full_path break if not self.libdir: @@ -410,7 +410,7 @@ class MPIDependency(ExternalDependency): for pkg in pkgconfig_files: try: - pkgdep = PkgConfigDependency(pkg, environment, kwargs) + pkgdep = PkgConfigDependency(pkg, environment, kwargs, language=self.language) if pkgdep.found(): self.compile_args = pkgdep.get_compile_args() self.link_args = pkgdep.get_link_args() @@ -730,11 +730,11 @@ class Python3Dependency(ExternalDependency): else: return [DependencyMethods.PKGCONFIG] - def get_pkgconfig_variable(self, variable_name): + def get_pkgconfig_variable(self, variable_name, kwargs): if self.pkgdep: - return self.pkgdep.get_pkgconfig_variable(variable_name) + return self.pkgdep.get_pkgconfig_variable(variable_name, kwargs) else: - return super().get_pkgconfig_variable(variable_name) + return super().get_pkgconfig_variable(variable_name, kwargs) class PcapDependency(ExternalDependency): diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index dd04580..1db518c 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -218,7 +218,8 @@ class QtBaseDependency(ExternalDependency): kwargs['required'] = False modules = OrderedDict() for module in mods: - modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, kwargs) + modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, + kwargs, language=self.language) for m in modules.values(): if not m.found(): self.is_found = False @@ -232,12 +233,13 @@ class QtBaseDependency(ExternalDependency): core = modules['Core'] else: corekwargs = {'required': 'false', 'silent': 'true'} - core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs) + core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs, + language=self.language) # Used by self.compilers_detect() self.bindir = self.get_pkgconfig_host_bins(core) if not self.bindir: # If exec_prefix is not defined, the pkg-config file is broken - prefix = core.get_pkgconfig_variable('exec_prefix') + prefix = core.get_pkgconfig_variable('exec_prefix', {}) if prefix: self.bindir = os.path.join(prefix, 'bin') @@ -357,7 +359,7 @@ class Qt4Dependency(QtBaseDependency): applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease'] for application in applications: try: - return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application)) + return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application, {})) except MesonException: pass @@ -367,7 +369,7 @@ class Qt5Dependency(QtBaseDependency): QtBaseDependency.__init__(self, 'qt5', env, kwargs) def get_pkgconfig_host_bins(self, core): - return core.get_pkgconfig_variable('host_bins') + return core.get_pkgconfig_variable('host_bins', {}) # There are three different ways of depending on SDL2: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 3e89305..f33d437 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -27,7 +27,7 @@ from .dependencies import InternalDependency, Dependency, DependencyException from .interpreterbase import InterpreterBase from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs, permittedKwargs from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode -from .interpreterbase import InterpreterObject, MutableInterpreterObject +from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler from .modules import ModuleReturnValue import os, sys, shutil, uuid @@ -295,7 +295,7 @@ class DependencyHolder(InterpreterObject, ObjectHolder): varname = args[0] if not isinstance(varname, str): raise InterpreterException('Variable name must be a string.') - return self.held_object.get_pkgconfig_variable(varname) + return self.held_object.get_pkgconfig_variable(varname, kwargs) def configtool_method(self, args, kwargs): args = listify(args) @@ -1451,6 +1451,7 @@ class Interpreter(InterpreterBase): 'custom_target': self.func_custom_target, 'declare_dependency': self.func_declare_dependency, 'dependency': self.func_dependency, + 'disabler': self.func_disabler, 'environment': self.func_environment, 'error': self.func_error, 'executable': self.func_executable, @@ -2146,6 +2147,12 @@ to directly access options of other subprojects.''') def func_dependency(self, node, args, kwargs): self.validate_arguments(args, 1, [str]) name = args[0] + + if name == '': + if kwargs.get('required', True): + raise InvalidArguments('Dependency is both required and not-found') + return DependencyHolder(Dependency('not-found', {})) + if '<' in name or '>' in name or '=' in name: raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify' 'version\n requirements use the \'version\' keyword argument instead.') @@ -2197,6 +2204,11 @@ to directly access options of other subprojects.''') self.coredata.deps[identifier] = dep return DependencyHolder(dep) + @noKwargs + @noPosargs + def func_disabler(self, node, args, kwargs): + return Disabler() + def get_subproject_infos(self, kwargs): fbinfo = kwargs['fallback'] check_stringlist(fbinfo) @@ -2999,12 +3011,19 @@ different subdirectory. def format_string(self, templ, args): if isinstance(args, mparser.ArgumentNode): args = args.arguments - for (i, arg) in enumerate(args): + arg_strings = [] + for arg in args: arg = self.evaluate_statement(arg) if isinstance(arg, bool): # Python boolean is upper case. arg = str(arg).lower() - templ = templ.replace('@{}@'.format(i), str(arg)) - return templ + arg_strings.append(str(arg)) + + def arg_replace(match): + idx = int(match.group(1)) + if idx >= len(arg_strings): + raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) + return arg_strings[idx] + return re.sub(r'@(\d+)@', arg_replace, templ) # Only permit object extraction from the same subproject def validate_extraction(self, buildtarget): diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 7ccc8b2..91f4bd3 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -100,6 +100,29 @@ class MutableInterpreterObject(InterpreterObject): def __init__(self): super().__init__() +class Disabler(InterpreterObject): + def __init__(self): + super().__init__() + self.methods.update({'found': self.found_method}) + + def found_method(self, args, kwargs): + return False + +def is_disabler(i): + return isinstance(i, Disabler) + +def is_disabled(args, kwargs): + for i in args: + if isinstance(i, Disabler): + return True + for i in kwargs.values(): + if isinstance(i, Disabler): + return True + if isinstance(i, list): + for j in i: + if isinstance(j, Disabler): + return True + return False class InterpreterBase: def __init__(self, source_root, subdir): @@ -230,6 +253,8 @@ class InterpreterBase: assert(isinstance(node, mparser.IfClauseNode)) for i in node.ifs: result = self.evaluate_statement(i.condition) + if is_disabler(result): + return result if not(isinstance(result, bool)): raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result)) if result: @@ -240,7 +265,11 @@ class InterpreterBase: def evaluate_comparison(self, node): val1 = self.evaluate_statement(node.left) + if is_disabler(val1): + return val1 val2 = self.evaluate_statement(node.right) + if is_disabler(val2): + return val2 if node.ctype == '==': return val1 == val2 elif node.ctype == '!=': @@ -267,35 +296,49 @@ class InterpreterBase: def evaluate_andstatement(self, cur): l = self.evaluate_statement(cur.left) + if is_disabler(l): + return l if not isinstance(l, bool): raise InterpreterException('First argument to "and" is not a boolean.') if not l: return False r = self.evaluate_statement(cur.right) + if is_disabler(r): + return r if not isinstance(r, bool): raise InterpreterException('Second argument to "and" is not a boolean.') return r def evaluate_orstatement(self, cur): l = self.evaluate_statement(cur.left) + if is_disabler(l): + return l if not isinstance(l, bool): raise InterpreterException('First argument to "or" is not a boolean.') if l: return True r = self.evaluate_statement(cur.right) + if is_disabler(r): + return r if not isinstance(r, bool): raise InterpreterException('Second argument to "or" is not a boolean.') return r def evaluate_uminusstatement(self, cur): v = self.evaluate_statement(cur.value) + if is_disabler(v): + return v if not isinstance(v, int): raise InterpreterException('Argument to negation is not an integer.') return -v def evaluate_arithmeticstatement(self, cur): l = self.evaluate_statement(cur.left) + if is_disabler(l): + return l r = self.evaluate_statement(cur.right) + if is_disabler(r): + return r if cur.operation == 'add': try: @@ -324,6 +367,8 @@ class InterpreterBase: def evaluate_ternary(self, node): assert(isinstance(node, mparser.TernaryNode)) result = self.evaluate_statement(node.condition) + if is_disabler(result): + return result if not isinstance(result, bool): raise InterpreterException('Ternary condition is not boolean.') if result: @@ -335,6 +380,8 @@ class InterpreterBase: assert(isinstance(node, mparser.ForeachClauseNode)) varname = node.varname.value items = self.evaluate_statement(node.items) + if is_disabler(items): + return items if not isinstance(items, list): raise InvalidArguments('Items of foreach loop is not an array') for item in items: @@ -345,6 +392,9 @@ class InterpreterBase: assert(isinstance(node, mparser.PlusAssignmentNode)) varname = node.var_name addition = self.evaluate_statement(node.value) + if is_disabler(addition): + set_variable(varname, addition) + return # Remember that all variables are immutable. We must always create a # full new variable and then assign it. old_variable = self.get_variable(varname) @@ -369,6 +419,8 @@ class InterpreterBase: def evaluate_indexing(self, node): assert(isinstance(node, mparser.IndexNode)) iobject = self.evaluate_statement(node.iobject) + if is_disabler(iobject): + return iobject if not hasattr(iobject, '__getitem__'): raise InterpreterException( 'Tried to index an object that doesn\'t support indexing.') @@ -383,6 +435,8 @@ class InterpreterBase: def function_call(self, node): func_name = node.func_name (posargs, kwargs) = self.reduce_arguments(node.args) + if is_disabled(posargs, kwargs): + return Disabler() if func_name in self.funcs: return self.funcs[func_name](node, self.flatten(posargs), kwargs) else: @@ -404,18 +458,26 @@ class InterpreterBase: if isinstance(obj, int): return self.int_method_call(obj, method_name, args) if isinstance(obj, list): - return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0]) + return self.array_method_call(obj, method_name, args) if isinstance(obj, mesonlib.File): raise InvalidArguments('File object "%s" is not callable.' % obj) if not isinstance(obj, InterpreterObject): raise InvalidArguments('Variable "%s" is not callable.' % object_name) (args, kwargs) = self.reduce_arguments(args) + # Special case. This is the only thing you can do with a disabler + # object. Every other use immediately returns the disabler object. + if isinstance(obj, Disabler) and method_name == 'found': + return False + if is_disabled(args, kwargs): + return Disabler() if method_name == 'extract_objects': self.validate_extraction(obj.held_object) return obj.method_call(method_name, self.flatten(args), kwargs) def bool_method_call(self, obj, method_name, args): - (posargs, _) = self.reduce_arguments(args) + (posargs, kwargs) = self.reduce_arguments(args) + if is_disabled(posargs, kwargs): + return Disabler() if method_name == 'to_string': if not posargs: if obj: @@ -438,7 +500,9 @@ class InterpreterBase: raise InterpreterException('Unknown method "%s" for a boolean.' % method_name) def int_method_call(self, obj, method_name, args): - (posargs, _) = self.reduce_arguments(args) + (posargs, kwargs) = self.reduce_arguments(args) + if is_disabled(posargs, kwargs): + return Disabler() if method_name == 'is_even': if not posargs: return obj % 2 == 0 @@ -471,7 +535,9 @@ class InterpreterBase: return None def string_method_call(self, obj, method_name, args): - (posargs, _) = self.reduce_arguments(args) + (posargs, kwargs) = self.reduce_arguments(args) + if is_disabled(posargs, kwargs): + return Disabler() if method_name == 'strip': s = self._get_one_string_posarg(posargs, 'strip') if s is not None: @@ -520,19 +586,22 @@ class InterpreterBase: raise InterpreterException('Unknown method "%s" for a string.' % method_name) def unknown_function_called(self, func_name): - raise InvalidCode('Unknown function "%s".' % func_name) + raise InvalidCode('Unknown function "%s".' % func_name) def array_method_call(self, obj, method_name, args): + (posargs, kwargs) = self.reduce_arguments(args) + if is_disabled(posargs, kwargs): + return Disabler() if method_name == 'contains': - return self.check_contains(obj, args) + return self.check_contains(obj, posargs) elif method_name == 'length': return len(obj) elif method_name == 'get': - index = args[0] + index = posargs[0] fallback = None - if len(args) == 2: - fallback = args[1] - elif len(args) > 2: + if len(posargs) == 2: + fallback = posargs[1] + elif len(posargs) > 2: m = 'Array method \'get()\' only takes two arguments: the ' \ 'index and an optional fallback value if the index is ' \ 'out of range.' diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index e0554e0..de788b7 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -40,10 +40,10 @@ class VisualStudioLinker(StaticLinker): return [] def get_always_args(self): - return VisualStudioLinker.always_args + return VisualStudioLinker.always_args[:] def get_linker_always_args(self): - return VisualStudioLinker.always_args + return VisualStudioLinker.always_args[:] def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): return [] diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index a35345b..09b5d92 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -744,6 +744,8 @@ def substitute_values(command, values): _substitute_values_check_errors(command, values) # Substitution outcmd = [] + rx_keys = [re.escape(key) for key in values if key not in ('@INPUT@', '@OUTPUT@')] + value_rx = re.compile('|'.join(rx_keys)) if rx_keys else None for vv in command: if not isinstance(vv, str): outcmd.append(vv) @@ -770,12 +772,9 @@ def substitute_values(command, values): elif vv in values: outcmd.append(values[vv]) # Substitute everything else with replacement + elif value_rx: + outcmd.append(value_rx.sub(lambda m: values[m.group(0)], vv)) else: - for key, value in values.items(): - if key in ('@INPUT@', '@OUTPUT@'): - # Already done above - continue - vv = vv.replace(key, value) outcmd.append(vv) return outcmd diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 568bdfc..b23869f 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -139,8 +139,8 @@ def add_keys(optlist, options): elif isinstance(opt, coredata.UserComboOption): optdict['choices'] = opt.choices typestr = 'combo' - elif isinstance(opt, coredata.UserStringArrayOption): - typestr = 'stringarray' + elif isinstance(opt, coredata.UserArrayOption): + typestr = 'array' else: raise RuntimeError("Unknown option type") optdict['type'] = typestr diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index f916c2c..56765a5 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -362,7 +362,7 @@ class GnomeModule(ExtensionModule): ldflags.update([lib]) if isinstance(dep, PkgConfigDependency): - girdir = dep.get_pkgconfig_variable("girdir") + girdir = dep.get_pkgconfig_variable("girdir", {}) if girdir: gi_includes.update([girdir]) elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): @@ -553,7 +553,7 @@ class GnomeModule(ExtensionModule): if subdir not in typelib_includes: typelib_includes.append(subdir) elif isinstance(dep, PkgConfigDependency): - girdir = dep.get_pkgconfig_variable("girdir") + girdir = dep.get_pkgconfig_variable("girdir", {}) if girdir and girdir not in typelib_includes: typelib_includes.append(girdir) # ldflags will be misinterpreted by gir scanner (showing diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 01dd036..f8ccbe6 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -84,9 +84,27 @@ def ComboParser(name, description, kwargs): raise OptionException('Combo choice elements must be strings.') return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0])) +@permitted_kwargs({'value', 'choices'}) +def string_array_parser(name, description, kwargs): + if 'choices' in kwargs: + choices = kwargs['choices'] + if not isinstance(choices, list): + raise OptionException('Array choices must be an array.') + for i in choices: + if not isinstance(i, str): + raise OptionException('Array choice elements must be strings.') + value = kwargs.get('value', choices) + else: + choices = None + value = kwargs.get('value', []) + if not isinstance(value, list): + raise OptionException('Array choices must be passed as an array.') + return coredata.UserArrayOption(name, description, value, choices=choices) + option_types = {'string': StringParser, 'boolean': BooleanParser, 'combo': ComboParser, + 'array': string_array_parser, } class OptionInterpreter: diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py index f99cddb..fc65e5b 100644 --- a/mesonbuild/scripts/commandrunner.py +++ b/mesonbuild/scripts/commandrunner.py @@ -31,15 +31,24 @@ def run_command(source_dir, build_dir, subdir, meson_command, command, arguments exe = shutil.which(command) if exe is not None: command_array = [exe] + arguments - return subprocess.Popen(command_array, env=child_env, cwd=cwd) - # No? Maybe it is a script in the source tree. - fullpath = os.path.join(source_dir, subdir, command) - command_array = [fullpath] + arguments + else:# No? Maybe it is a script in the source tree. + fullpath = os.path.join(source_dir, subdir, command) + command_array = [fullpath] + arguments try: return subprocess.Popen(command_array, env=child_env, cwd=cwd) except FileNotFoundError: - print('Could not execute command "%s".' % command) + print('Could not execute command "%s". File not found.' % command) + sys.exit(1) + except PermissionError: + print('Could not execute command "%s". File not executable.' % command) + sys.exit(1) + except OSError as err: + print('Could not execute command "{}": {}'.format(command, err)) sys.exit(1) + except subprocess.SubprocessError as err: + print('Could not execute command "{}": {}'.format(command, err)) + sys.exit(1) + def run(args): if len(args) < 4: diff --git a/run_tests.py b/run_tests.py index b287a1a..1cc3983 100755 --- a/run_tests.py +++ b/run_tests.py @@ -155,15 +155,6 @@ def run_configure(meson_command, commandlist): return run_configure_external(meson_exe + commandlist) return run_configure_inprocess(meson_command, commandlist) -class FakeEnvironment(object): - def __init__(self): - self.cross_info = None - self.coredata = lambda: None - self.coredata.compilers = {} - - def is_cross_build(self): - return False - def print_system_info(): print(mlog.bold('System information.').get_text(mlog.colorize_console)) print('Architecture:', platform.architecture()) diff --git a/run_unittests.py b/run_unittests.py index 5b3b3e1..f717e6d 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1,4 +1,3 @@ - #!/usr/bin/env python3 # Copyright 2016-2017 The Meson development team @@ -36,12 +35,12 @@ import mesonbuild.mesonlib import mesonbuild.coredata from mesonbuild.interpreter import ObjectHolder from mesonbuild.mesonlib import is_linux, is_windows, is_osx, is_cygwin, windows_proof_rmtree -from mesonbuild.mesonlib import python_command, meson_command +from mesonbuild.mesonlib import python_command, meson_command, version_compare from mesonbuild.environment import Environment from mesonbuild.dependencies import DependencyException from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram -from run_tests import exe_suffix, get_fake_options, FakeEnvironment +from run_tests import exe_suffix, get_fake_options from run_tests import get_builddir_target_args, get_backend_commands, Backend from run_tests import ensure_backend_detects_changes, run_configure, meson_exe from run_tests import should_run_linux_cross_tests @@ -59,7 +58,7 @@ def get_dynamic_section_entry(fname, entry): m = pattern.search(line) if m is not None: return m.group(1) - raise RuntimeError('Could not determine {}:\n\n'.format(entry) + raw_out) + return None # The file did not contain the specified entry. def get_soname(fname): return get_dynamic_section_entry(fname, 'soname') @@ -1062,16 +1061,17 @@ class AllPlatformTests(BasePlatformTests): evalue = os.environ.pop(evar) # Very rough/strict heuristics. Would never work for actual # compiler detection, but should be ok for the tests. - if os.path.basename(evalue).startswith('g'): + ebase = os.path.basename(evalue) + if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')): self.assertIsInstance(ecc, gnu) self.assertIsInstance(elinker, ar) - elif 'clang' in os.path.basename(evalue): + elif 'clang' in ebase: self.assertIsInstance(ecc, clang) self.assertIsInstance(elinker, ar) - elif os.path.basename(evalue).startswith('ic'): + elif ebase.startswith('ic'): self.assertIsInstance(ecc, intel) self.assertIsInstance(elinker, ar) - elif os.path.basename(evalue).startswith('cl'): + elif ebase.startswith('cl'): self.assertIsInstance(ecc, msvc) self.assertIsInstance(elinker, lib) else: @@ -1287,13 +1287,15 @@ class AllPlatformTests(BasePlatformTests): raise unittest.SkipTest('Git not found') def git_init(project_dir): - subprocess.check_call(['git', 'init'], cwd=project_dir) + subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL) subprocess.check_call(['git', 'config', 'user.name', 'Author Person'], cwd=project_dir) subprocess.check_call(['git', 'config', 'user.email', 'teh_coderz@example.com'], cwd=project_dir) - subprocess.check_call(['git', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) - subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir) + subprocess.check_call(['git', 'add', 'meson.build', 'distexe.c'], cwd=project_dir, + stdout=subprocess.DEVNULL) + subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir, + stdout=subprocess.DEVNULL) try: self.dist_impl(git_init) @@ -1361,12 +1363,15 @@ int main(int argc, char **argv) { testdir = os.path.join(self.common_test_dir, '46 library chain') self.init(testdir) self.build() - for each in ('prog', 'subdir/liblib1.so', 'subdir/subdir2/liblib2.so', - 'subdir/subdir3/liblib3.so'): + for each in ('prog', 'subdir/liblib1.so', ): rpath = get_rpath(os.path.join(self.builddir, each)) self.assertTrue(rpath) for path in rpath.split(':'): self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path)) + # These two don't link to anything else, so they do not need an rpath entry. + for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'): + rpath = get_rpath(os.path.join(self.builddir, each)) + self.assertTrue(rpath is None) def test_dash_d_dedup(self): testdir = os.path.join(self.unit_test_dir, '10 d dedup') @@ -1396,6 +1401,7 @@ int main(int argc, char **argv) { env = Environment('', self.builddir, self.meson_command, get_fake_options(self.prefix), []) cc = env.detect_c_compiler(False) + stlinker = env.detect_static_linker(cc) if mesonbuild.mesonlib.is_windows(): object_suffix = 'obj' shared_suffix = 'dll' @@ -1408,7 +1414,7 @@ int main(int argc, char **argv) { else: object_suffix = 'o' shared_suffix = 'so' - return (cc, object_suffix, shared_suffix) + return (cc, stlinker, object_suffix, shared_suffix) def pbcompile(self, compiler, source, objectfile, extra_args=[]): cmd = compiler.get_exelist() @@ -1420,7 +1426,7 @@ int main(int argc, char **argv) { def test_prebuilt_object(self): - (compiler, object_suffix, _) = self.detect_prebuild_env() + (compiler, _, object_suffix, _) = self.detect_prebuild_env() tdir = os.path.join(self.unit_test_dir, '14 prebuilt object') source = os.path.join(tdir, 'source.c') objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix) @@ -1432,13 +1438,18 @@ int main(int argc, char **argv) { finally: os.unlink(objectfile) - def build_static_lib(self, compiler, source, objectfile, outfile, extra_args=None): + def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None): if extra_args is None: extra_args = [] if compiler.id == 'msvc': link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile] else: link_cmd = ['ar', 'csr', outfile, objectfile] + link_cmd = linker.get_exelist() + link_cmd += linker.get_always_args() + link_cmd += linker.get_std_link_args() + link_cmd += linker.get_output_args(outfile) + link_cmd += [objectfile] self.pbcompile(compiler, source, objectfile, extra_args=extra_args) try: subprocess.check_call(link_cmd) @@ -1446,12 +1457,12 @@ int main(int argc, char **argv) { os.unlink(objectfile) def test_prebuilt_static_lib(self): - (cc, object_suffix, _) = self.detect_prebuild_env() + (cc, stlinker, object_suffix, _) = self.detect_prebuild_env() tdir = os.path.join(self.unit_test_dir, '15 prebuilt static') source = os.path.join(tdir, 'libdir/best.c') objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix) stlibfile = os.path.join(tdir, 'libdir/libbest.a') - self.build_static_lib(cc, source, objectfile, stlibfile) + self.build_static_lib(cc, stlinker, source, objectfile, stlibfile) # Run the test try: self.init(tdir) @@ -1478,7 +1489,7 @@ int main(int argc, char **argv) { os.unlink(objectfile) def test_prebuilt_shared_lib(self): - (cc, object_suffix, shared_suffix) = self.detect_prebuild_env() + (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env() tdir = os.path.join(self.unit_test_dir, '16 prebuilt shared') source = os.path.join(tdir, 'alexandria.c') objectfile = os.path.join(tdir, 'alexandria.' + object_suffix) @@ -1512,7 +1523,7 @@ int main(int argc, char **argv) { ''' if not shutil.which('pkg-config'): raise unittest.SkipTest('pkg-config not found') - (cc, objext, shext) = self.detect_prebuild_env() + (cc, stlinker, objext, shext) = self.detect_prebuild_env() testdir = os.path.join(self.unit_test_dir, '17 pkgconfig static') source = os.path.join(testdir, 'foo.c') objectfile = os.path.join(testdir, 'foo.' + objext) @@ -1525,7 +1536,7 @@ int main(int argc, char **argv) { else: shlibfile = os.path.join(testdir, 'libfoo.' + shext) # Build libs - self.build_static_lib(cc, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC']) + self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC']) self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) # Run test os.environ['PKG_CONFIG_LIBDIR'] = self.builddir @@ -1553,7 +1564,8 @@ int main(int argc, char **argv) { '--libdir=' + libdir]) # Find foo dependency os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir - env = FakeEnvironment() + env = Environment(testdir, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) kwargs = {'required': True, 'silent': True} foo_dep = PkgConfigDependency('libfoo', env, kwargs) # Ensure link_args are properly quoted @@ -1565,6 +1577,72 @@ int main(int argc, char **argv) { cargs = ['-I' + incdir.as_posix()] self.assertEqual(foo_dep.get_compile_args(), cargs) + def test_array_option_change(self): + def get_opt(): + opts = self.introspect('--buildoptions') + for x in opts: + if x.get('name') == 'list': + return x + raise Exception(opts) + + expected = { + 'name': 'list', + 'description': 'list', + 'type': 'array', + 'value': ['foo', 'bar'], + } + tdir = os.path.join(self.unit_test_dir, '18 array option') + self.init(tdir) + original = get_opt() + self.assertDictEqual(original, expected) + + expected['value'] = ['oink', 'boink'] + self.setconf('-Dlist=oink,boink') + changed = get_opt() + self.assertEqual(changed, expected) + + def test_array_option_bad_change(self): + def get_opt(): + opts = self.introspect('--buildoptions') + for x in opts: + if x.get('name') == 'list': + return x + raise Exception(opts) + + expected = { + 'name': 'list', + 'description': 'list', + 'type': 'array', + 'value': ['foo', 'bar'], + } + tdir = os.path.join(self.unit_test_dir, '18 array option') + self.init(tdir) + original = get_opt() + self.assertDictEqual(original, expected) + with self.assertRaises(subprocess.CalledProcessError): + self.setconf('-Dlist=bad') + changed = get_opt() + self.assertDictEqual(changed, expected) + + def opt_has(self, name, value): + res = self.introspect('--buildoptions') + found = False + for i in res: + if i['name'] == name: + self.assertEqual(i['value'], value) + found = True + break + self.assertTrue(found, "Array option not found in introspect data.") + + def test_free_stringarray_setting(self): + testdir = os.path.join(self.common_test_dir, '47 options') + self.init(testdir) + self.opt_has('free_array_opt', []) + self.setconf('-Dfree_array_opt=foo,bar', will_build=False) + self.opt_has('free_array_opt', ['foo', 'bar']) + self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False) + self.opt_has('free_array_opt', ['a,b', 'c,d']) + class FailureTests(BasePlatformTests): ''' @@ -1688,6 +1766,22 @@ class FailureTests(BasePlatformTests): self.assertMesonRaises("dependency('boost')", "(BOOST_ROOT.*absolute|{})".format(self.dnf)) + def test_dependency_invalid_method(self): + code = '''zlib_dep = dependency('zlib', required : false) + zlib_dep.get_configtool_variable('foo') + ''' + self.assertMesonRaises(code, "'zlib' is not a config-tool dependency") + code = '''zlib_dep = dependency('zlib', required : false) + dep = declare_dependency(dependencies : zlib_dep) + dep.get_pkgconfig_variable('foo') + ''' + self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal") + code = '''zlib_dep = dependency('zlib', required : false) + dep = declare_dependency(dependencies : zlib_dep) + dep.get_configtool_variable('foo') + ''' + self.assertMesonRaises(code, "Method.*configtool.*is invalid.*internal") + class WindowsTests(BasePlatformTests): ''' @@ -1807,15 +1901,16 @@ class LinuxlikeTests(BasePlatformTests): ''' testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen') self.init(testdir) - env = FakeEnvironment() + env = Environment(testdir, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) kwargs = {'required': True, 'silent': True} os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir foo_dep = PkgConfigDependency('libfoo', env, kwargs) self.assertTrue(foo_dep.found()) self.assertEqual(foo_dep.get_version(), '1.0') self.assertIn('-lfoo', foo_dep.get_link_args()) - self.assertEqual(foo_dep.get_pkgconfig_variable('foo'), 'bar') - self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir'), '/usr/data') + self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar') + self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data') def test_vala_c_warnings(self): ''' @@ -1969,6 +2064,8 @@ class LinuxlikeTests(BasePlatformTests): # Check that all the listed -std=xxx options for this compiler work # just fine when used for v in compiler.get_options()[lang_std].choices: + if compiler.get_id() == 'clang' and version_compare(compiler.version, '<5.0.0') and '17' in v: + continue std_opt = '{}={}'.format(lang_std, v) self.init(testdir, ['-D' + std_opt]) cmd = self.get_compdb()[0]['command'] @@ -2191,8 +2288,9 @@ class LinuxlikeTests(BasePlatformTests): 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.') + if 'clang' in os.environ.get('CC', ''): + # We need to use llvm-cov instead of gcovr with clang + raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!') testdir = os.path.join(self.common_test_dir, '1 trivial') self.init(testdir, ['-Db_coverage=true']) self.build() diff --git a/test cases/common/166 custom target subdir depend files/copyfile.py b/test cases/common/166 custom target subdir depend files/copyfile.py new file mode 100644 index 0000000..ff42ac3 --- /dev/null +++ b/test cases/common/166 custom target subdir depend files/copyfile.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +import shutil + +shutil.copyfile(sys.argv[1], sys.argv[2]) diff --git a/test cases/common/166 custom target subdir depend files/meson.build b/test cases/common/166 custom target subdir depend files/meson.build new file mode 100644 index 0000000..44f5c71 --- /dev/null +++ b/test cases/common/166 custom target subdir depend files/meson.build @@ -0,0 +1,7 @@ +project('custom target subdir depend files', 'c') + +copy = find_program('copyfile.py') + +subdir('subdir') + +executable('foo', foo_src) diff --git a/test cases/common/166 custom target subdir depend files/subdir/dep.dat b/test cases/common/166 custom target subdir depend files/subdir/dep.dat new file mode 100644 index 0000000..5daee49 --- /dev/null +++ b/test cases/common/166 custom target subdir depend files/subdir/dep.dat @@ -0,0 +1 @@ +You can depend on this file.
\ No newline at end of file diff --git a/test cases/common/166 custom target subdir depend files/subdir/foo.c.in b/test cases/common/166 custom target subdir depend files/subdir/foo.c.in new file mode 100644 index 0000000..d53846f --- /dev/null +++ b/test cases/common/166 custom target subdir depend files/subdir/foo.c.in @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main() { + printf("foo is working.\n"); + return 0; +} diff --git a/test cases/common/166 custom target subdir depend files/subdir/meson.build b/test cases/common/166 custom target subdir depend files/subdir/meson.build new file mode 100644 index 0000000..f9d31c4 --- /dev/null +++ b/test cases/common/166 custom target subdir depend files/subdir/meson.build @@ -0,0 +1,6 @@ +foo_src = custom_target('foo_src', + depend_files : 'dep.dat', + input : 'foo.c.in', + output : 'foo.c', + command : [copy, '@INPUT@', '@OUTPUT@'] +) diff --git a/test cases/common/168 disabler/meson.build b/test cases/common/168 disabler/meson.build new file mode 100644 index 0000000..7ca82b7 --- /dev/null +++ b/test cases/common/168 disabler/meson.build @@ -0,0 +1,34 @@ +project('dolphin option', 'c') + +d = disabler() + +d2 = dependency(d) +d3 = (d == d2) +d4 = d + 0 +d5 = d2 or true + +assert(d, 'Disabler did not cause this to be skipped.') +assert(d2, 'Function laundered disabler did not cause this to be skipped.') +assert(d3, 'Disabler comparison should yield disabler and thus this would not be called.') +assert(d4, 'Disabler addition should yield disabler and thus this would not be called.') +assert(d5, 'Disabler logic op should yield disabler and thus this would not be called.') + +number = 0 + +if d + number = 1 +else + number = 2 +endif + +assert(d == 0, 'Plain if handled incorrectly, value should be 0 but is @0@'.format(number)) + +if d.found() + number = 1 +else + number = 2 +endif + +assert(d == 1, 'If found handled incorrectly, value should be 1 but is @0@'.format(number)) + + diff --git a/test cases/common/169 array option/meson.build b/test cases/common/169 array option/meson.build new file mode 100644 index 0000000..034b9a5 --- /dev/null +++ b/test cases/common/169 array option/meson.build @@ -0,0 +1,17 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +project('array default options') + +assert(get_option('array') == ['foo', 'bar'], 'Default value for array is not equal to choices') diff --git a/test cases/common/169 array option/meson_options.txt b/test cases/common/169 array option/meson_options.txt new file mode 100644 index 0000000..7ed0ac1 --- /dev/null +++ b/test cases/common/169 array option/meson_options.txt @@ -0,0 +1,19 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +option( + 'array', + type : 'array', + choices : ['foo', 'bar'], +) diff --git a/test cases/common/170 custom target template substitution/checkcopy.py b/test cases/common/170 custom target template substitution/checkcopy.py new file mode 100644 index 0000000..ab9f436 --- /dev/null +++ b/test cases/common/170 custom target template substitution/checkcopy.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +import sys +import shutil + +if '@INPUT1@' in sys.argv[1]: + shutil.copyfile(sys.argv[2], sys.argv[3]) +else: + sys.exit('String @INPUT1@ not found in "{}"'.format(sys.argv[1])) diff --git a/test cases/common/170 custom target template substitution/foo.c.in b/test cases/common/170 custom target template substitution/foo.c.in new file mode 100644 index 0000000..d53846f --- /dev/null +++ b/test cases/common/170 custom target template substitution/foo.c.in @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main() { + printf("foo is working.\n"); + return 0; +} diff --git a/test cases/common/170 custom target template substitution/meson.build b/test cases/common/170 custom target template substitution/meson.build new file mode 100644 index 0000000..3f6a159 --- /dev/null +++ b/test cases/common/170 custom target template substitution/meson.build @@ -0,0 +1,17 @@ +project('custom target template substitution', 'c') + +check = find_program('checkcopy.py') + +config = configuration_data() + +in = configure_file(configuration : config, output : 'x@IN') + +# Check that substitution does not find @FOO@ and then misses @INPUT0@. +# Check the resulting x@INPUT1@ is not replaced. +foo = custom_target('runcheck', + input : [in, 'foo.c.in'], + output : 'foo.c', + command : [check, '-D@FOO@INPUT0@PUT1@', '@INPUT1@', '@OUTPUT@'] +) + +executable('foo', foo) diff --git a/test cases/common/171 not-found dependency/meson.build b/test cases/common/171 not-found dependency/meson.build new file mode 100644 index 0000000..7d92f5a --- /dev/null +++ b/test cases/common/171 not-found dependency/meson.build @@ -0,0 +1,8 @@ +project('dep-test') + +dep = dependency('', required:false) +if dep.found() + error('not-found dependency was found') +endif + +assert(dep.type_name() == 'not-found', 'dependency should be of type "not-found" not ' + dep.type_name()) diff --git a/test cases/common/165 subdir if_found/meson.build b/test cases/common/172 subdir if_found/meson.build index 2c640cf..2c640cf 100644 --- a/test cases/common/165 subdir if_found/meson.build +++ b/test cases/common/172 subdir if_found/meson.build diff --git a/test cases/common/165 subdir if_found/subdir/meson.build b/test cases/common/172 subdir if_found/subdir/meson.build index 1030e25..1030e25 100644 --- a/test cases/common/165 subdir if_found/subdir/meson.build +++ b/test cases/common/172 subdir if_found/subdir/meson.build diff --git a/test cases/common/42 string operations/meson.build b/test cases/common/42 string operations/meson.build index e60006a..a43de70 100644 --- a/test cases/common/42 string operations/meson.build +++ b/test cases/common/42 string operations/meson.build @@ -13,6 +13,8 @@ subs2 = '42' assert(templ2.format(subs2) == '42', 'String formatting with variables is broken.') +assert('@@0@@ @@1@@'.format(1, 2) == '@1@ @2@', 'String format is recursive.') + long = 'abcde' prefix = 'abc' suffix = 'cde' diff --git a/test cases/common/47 options/meson.build b/test cases/common/47 options/meson.build index 2a764f0..863703c 100644 --- a/test cases/common/47 options/meson.build +++ b/test cases/common/47 options/meson.build @@ -12,6 +12,11 @@ if get_option('combo_opt') != 'combo' error('Incorrect value to combo option.') endif +if get_option('array_opt') != ['one', 'two'] + message(get_option('array_opt')) + error('Incorrect value for array option') +endif + # If the default changes, update test cases/unit/13 reconfigure if get_option('b_lto') != false error('Incorrect value in base option.') diff --git a/test cases/common/47 options/meson_options.txt b/test cases/common/47 options/meson_options.txt index 653dd75..6bd0346 100644 --- a/test cases/common/47 options/meson_options.txt +++ b/test cases/common/47 options/meson_options.txt @@ -1,3 +1,5 @@ option('testoption', type : 'string', value : 'optval', description : 'An option to do something') option('other_one', type : 'boolean', value : false) option('combo_opt', type : 'combo', choices : ['one', 'two', 'combo'], value : 'combo') +option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two']) +option('free_array_opt', type : 'array') diff --git a/test cases/failing/67 dependency not-found and required/meson.build b/test cases/failing/67 dependency not-found and required/meson.build new file mode 100644 index 0000000..1ce5747 --- /dev/null +++ b/test cases/failing/67 dependency not-found and required/meson.build @@ -0,0 +1,2 @@ +project('dep-test') +dep = dependency('', required:true) diff --git a/test cases/linuxlike/1 pkg-config/meson.build b/test cases/linuxlike/1 pkg-config/meson.build index 17feb21..30e5d25 100644 --- a/test cases/linuxlike/1 pkg-config/meson.build +++ b/test cases/linuxlike/1 pkg-config/meson.build @@ -17,6 +17,8 @@ test('zlibtest', exe) zprefix = dep.get_pkgconfig_variable('prefix') # Always set but we can't be sure what the value is. # pkg-config returns empty string for not defined variables assert(dep.get_pkgconfig_variable('nonexisting') == '', 'Value of unknown variable is not empty.') +# pkg-config is able to replace variables +assert(dep.get_pkgconfig_variable('prefix', define_variable: ['prefix', '/tmp']) == '/tmp', 'prefix variable has not been replaced.') # Test that dependencies of dependencies work. dep2 = declare_dependency(dependencies : dep) diff --git a/test cases/osx/2 library versions/CMakeLists.txt b/test cases/osx/2 library versions/CMakeLists.txt index 82fe1e2..2cd03b9 100644 --- a/test cases/osx/2 library versions/CMakeLists.txt +++ b/test cases/osx/2 library versions/CMakeLists.txt @@ -4,23 +4,26 @@ project(dylibversion C) # This file is here for debugging purposes to easily compare how # CMake does it. +# libnoversion.dylib add_library(noversion SHARED lib.c) # libonlysoversion.dylib -> libonlysoversion.5.dylib # libonlyversion.1.4.5.dylib +# -current_version 1.4.5 add_library(onlyversion SHARED lib.c) set_target_properties(onlyversion PROPERTIES VERSION 1.4.5) -# libnoversion.dylib -# libonlysoversion.5.dylib +# libonlysoversion.6.dylib +# -compatibility_version 6.0.0 add_library(onlysoversion SHARED lib.c) -set_target_properties(onlysoversion PROPERTIES SOVERSION 5) +set_target_properties(onlysoversion PROPERTIES SOVERSION 6) # libsome.1.4.5.dylib -# libsome.5.dylib -> libsome.1.4.5.dylib -# libsome.dylib -> libsome.5.dylib +# libsome.6.dylib -> libsome.1.4.5.dylib +# libsome.dylib -> libsome.6.dylib +# -current_version 1.4.5 -compatibility_version 5.0.0 add_library(some SHARED lib.c) -set_target_properties(some PROPERTIES VERSION 1.4.5 SOVERSION 5) +set_target_properties(some PROPERTIES VERSION 1.4.5 SOVERSION 6) diff --git a/test cases/osx/2 library versions/meson.build b/test cases/osx/2 library versions/meson.build index 9624998..3061ed6 100644 --- a/test cases/osx/2 library versions/meson.build +++ b/test cases/osx/2 library versions/meson.build @@ -29,15 +29,19 @@ out = custom_target('library-dependency-hack', # Manually test if the linker can find the above libraries # i.e., whether they were generated with the right naming scheme test('manually linked 1', executable('manuallink1', out, - link_args : ['-L.', '-lsome'])) + link_args : ['-L.', '-lsome'], + build_rpath : meson.current_build_dir())) test('manually linked 2', executable('manuallink2', out, - link_args : ['-L.', '-lnoversion'])) + link_args : ['-L.', '-lnoversion'], + build_rpath : meson.current_build_dir())) test('manually linked 3', executable('manuallink3', out, - link_args : ['-L.', '-lonlyversion'])) + link_args : ['-L.', '-lonlyversion'], + build_rpath : meson.current_build_dir())) test('manually linked 4', executable('manuallink4', out, - link_args : ['-L.', '-lonlysoversion'])) + link_args : ['-L.', '-lonlysoversion'], + build_rpath : meson.current_build_dir())) shared_module('module', 'lib.c', install : true) diff --git a/test cases/osx/3 has function xcode8/meson.build b/test cases/osx/3 has function xcode8/meson.build index 0df7365..edd3688 100644 --- a/test cases/osx/3 has function xcode8/meson.build +++ b/test cases/osx/3 has function xcode8/meson.build @@ -12,7 +12,7 @@ sdk_args = ['-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/M args_10_12 = ['-mmacosx-version-min=10.13'] + sdk_args # Test requires XCode 8 which has the MacOSX 10.12 SDK -if cc.version().version_compare('>=8.0') and cc.version().version_compare('<9.0') +if cc.version().version_compare('>=8.0') and cc.version().version_compare('<8.1') if cc.has_function('clock_gettime', args : args_10_11, prefix : '#include <time.h>') error('Should not have found clock_gettime via <time.h> when targeting Mac OS X 10.11') endif diff --git a/test cases/unit/17 pkgconfig static/meson.build b/test cases/unit/17 pkgconfig static/meson.build index caeb4aa..d1b0fd5 100644 --- a/test cases/unit/17 pkgconfig static/meson.build +++ b/test cases/unit/17 pkgconfig static/meson.build @@ -5,8 +5,22 @@ if build_machine.system() != 'windows' else # pkg-config files should not use paths with \ prefix_parts = meson.source_root().split('\\') - prefix = '/'.join(prefix_parts) + # If the path is C:/foo/bar, convert it to /c/foo/bar so we can test if our + # automatic conversion to C:/foo/bar inside PkgConfigDependency is working. + if prefix_parts[0][1] == ':' + drive = prefix_parts[0][0] + else + drive = prefix_parts[0] + endif + new_parts = [] + foreach part : prefix_parts + if part != prefix_parts[0] + new_parts += part + endif + endforeach + prefix = '/@0@/@1@'.format(drive, '/'.join(new_parts)) endif +message(prefix) # Escape spaces prefix_parts = prefix.split(' ') diff --git a/test cases/unit/18 array option/meson.build b/test cases/unit/18 array option/meson.build new file mode 100644 index 0000000..2b44181 --- /dev/null +++ b/test cases/unit/18 array option/meson.build @@ -0,0 +1,15 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +project('array option test') diff --git a/test cases/unit/18 array option/meson_options.txt b/test cases/unit/18 array option/meson_options.txt new file mode 100644 index 0000000..0ccdcc4 --- /dev/null +++ b/test cases/unit/18 array option/meson_options.txt @@ -0,0 +1,20 @@ +# Copyright © 2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +option( + 'list', + type : 'array', + value : ['foo', 'bar'], + choices : ['foo', 'bar', 'oink', 'boink'], +) |