diff options
211 files changed, 4835 insertions, 1165 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index a6b91b2..a1a9c5f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -64,14 +64,35 @@ init: } install: + - ps: | + function DownloadFile([String] $Source, [String] $Destination) { + $retries = 10 + for ($i = 1; $i -le $retries; $i++) { + try { + (New-Object net.webclient).DownloadFile($Source, $Destination) + break # succeeded + } catch [net.WebException] { + if ($i -eq $retries) { + throw # fail on last retry + } + $backoff = (10 * $i) # backoff 10s, 20s, 30s... + echo ('{0}: {1}' -f $Source, $_.Exception.Message) + echo ('Retrying in {0}s...' -f $backoff) + Start-Sleep -m ($backoff * 1000) + } + } + } - cmd: set "ORIG_PATH=%PATH%" # Use a Ninja with QuLogic's patch: https://github.com/ninja-build/ninja/issues/1219 - cmd: set "MESON_FIXED_NINJA=1" - - ps: (new-object net.webclient).DownloadFile('http://nirbheek.in/files/binaries/ninja/win32/ninja.exe', 'C:\projects\meson\ninja.exe') + - ps: DownloadFile -Source 'http://nirbheek.in/files/binaries/ninja/win32/ninja.exe' -Destination 'C:\projects\meson\ninja.exe' # Use the x86 python only when building for x86 for the cpython tests. # For all other archs (including, say, arm), use the x64 python. - cmd: if %arch%==x86 (set MESON_PYTHON_PATH=C:\python35) else (set MESON_PYTHON_PATH=C:\python35-x64) + # Skip CI requires python + - cmd: python ./skip_ci.py --base-branch-env=APPVEYOR_REPO_BRANCH --is-pull-env=APPVEYOR_PULL_REQUEST_NUMBER + # Set paths for BOOST dll files - cmd: if %compiler%==msvc2015 ( if %arch%==x86 ( set "PATH=%PATH%;C:\Libraries\boost_1_59_0\lib32-msvc-14.0" ) else ( set "PATH=%PATH%;C:\Libraries\boost_1_59_0\lib64-msvc-14.0" ) ) - cmd: if %compiler%==msvc2017 ( if %arch%==x86 ( set "PATH=%PATH%;C:\Libraries\boost_1_64_0\lib32-msvc-14.1" ) else ( set "PATH=%PATH%;C:\Libraries\boost_1_64_0\lib64-msvc-14.1" ) ) @@ -94,17 +115,19 @@ install: - cmd: if %compiler%==cygwin ( %WRAPPER% which %PYTHON% ) else ( where %PYTHON% ) # pkg-config is needed for the pkg-config tests on msvc - - ps: If($Env:compiler.StartsWith('msvc')) {(new-object net.webclient).DownloadFile('http://nirbheek.in/files/binaries/pkg-config/win32/pkg-config.exe', 'C:\projects\meson\pkg-config.exe')} + - ps: | + If($Env:compiler.StartsWith('msvc')) { + DownloadFile -Source 'http://nirbheek.in/files/binaries/pkg-config/win32/pkg-config.exe' ` + -Destination 'C:\projects\meson\pkg-config.exe' + } - cmd: if %compiler%==cygwin ( call ci\appveyor-install.bat ) - ps: | If($Env:compiler -like 'msvc*') { - (new-object net.webclient).DownloadFile( - "https://download.microsoft.com/download/D/B/B/DBB64BA1-7B51-43DB-8BF1-D1FB45EACF7A/msmpisdk.msi", - "C:\projects\msmpisdk.msi") + DownloadFile -Source "https://download.microsoft.com/download/D/B/B/DBB64BA1-7B51-43DB-8BF1-D1FB45EACF7A/msmpisdk.msi" ` + -Destination "C:\projects\msmpisdk.msi" c:\windows\system32\msiexec.exe /i C:\projects\msmpisdk.msi /quiet - (new-object net.webclient).DownloadFile( - "https://download.microsoft.com/download/D/B/B/DBB64BA1-7B51-43DB-8BF1-D1FB45EACF7A/MSMpiSetup.exe", - "C:\projects\MSMpiSetup.exe") + DownloadFile -Source "https://download.microsoft.com/download/D/B/B/DBB64BA1-7B51-43DB-8BF1-D1FB45EACF7A/MSMpiSetup.exe" ` + -Destination "C:\projects\MSMpiSetup.exe" c:\projects\MSMpiSetup.exe -unattend -full } diff --git a/.travis.yml b/.travis.yml index e93e0e7..16fa55b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ matrix: compiler: gcc before_install: + - python ./skip_ci.py --base-branch-env=TRAVIS_BRANCH --is-pull-env=TRAVIS_PULL_REQUEST - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew uninstall python mercurial; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install python@2 python@3 mercurial qt; fi diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile index 72788c3..8573367 100644 --- a/ciimage/Dockerfile +++ b/ciimage/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get -y update && apt-get -y upgrade \ && apt-get -y install gtk-sharp2 gtk-sharp2-gapi libglib2.0-cil-dev \ && apt-get -y install libwmf-dev \ && apt-get -y install qt4-linguist-tools qttools5-dev-tools \ +&& apt-get -y install python-dev \ && python3 -m pip install hotdoc codecov ENV LANG='C.UTF-8' diff --git a/cross/armcc.txt b/cross/armcc.txt new file mode 100644 index 0000000..c884ffa --- /dev/null +++ b/cross/armcc.txt @@ -0,0 +1,20 @@ +# This file assumes that path to the arm compiler toolchain is added +# to the environment(PATH) variable, so that Meson can find +# the armcc, armlink and armar while building. +[binaries] +c = 'armcc' +cpp = 'armcc' +ar = 'armar' +strip = 'armar' + +[properties] +# The '--cpu' option with the appropriate target type should be mentioned +# to cross compile c/c++ code with armcc,. +c_args = ['--cpu=Cortex-M0plus'] +cpp_args = ['--cpu=Cortex-M0plus'] + +[host_machine] +system = 'bare metal' # Update with your system name - bare metal/OS. +cpu_family = 'arm' +cpu = 'Cortex-M0+' +endian = 'little' diff --git a/cross/ubuntu-armhf.txt b/cross/ubuntu-armhf.txt index 6246ffe..fec8ce7 100644 --- a/cross/ubuntu-armhf.txt +++ b/cross/ubuntu-armhf.txt @@ -1,8 +1,8 @@ [binaries] # we could set exe_wrapper = qemu-arm-static but to test the case # when cross compiled binaries can't be run we don't do that -c = '/usr/bin/arm-linux-gnueabihf-gcc-7' -cpp = '/usr/bin/arm-linux-gnueabihf-g++-7' +c = '/usr/bin/arm-linux-gnueabihf-gcc' +cpp = '/usr/bin/arm-linux-gnueabihf-g++' rust = ['rustc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7'] ar = '/usr/arm-linux-gnueabihf/bin/ar' strip = '/usr/arm-linux-gnueabihf/bin/strip' diff --git a/data/shell-completions/zsh/_meson b/data/shell-completions/zsh/_meson index 877d700..481d04c 100644 --- a/data/shell-completions/zsh/_meson +++ b/data/shell-completions/zsh/_meson @@ -31,7 +31,7 @@ local -i ret local __meson_backends="(ninja xcode ${(j. .)${:-vs{,2010,2015,2017}}})" local __meson_build_types="(plain debug debugoptimized minsize release)" -local __meson_wrap_modes="(WrapMode.{default,nofallback,nodownload})" +local __meson_wrap_modes="(WrapMode.{default,nofallback,nodownload,forcefallback})" local -a meson_commands=( 'setup:set up a build directory' diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim index 83dd66a..d58903e 100644 --- a/data/syntax-highlighting/vim/syntax/meson.vim +++ b/data/syntax-highlighting/vim/syntax/meson.vim @@ -70,6 +70,7 @@ syn keyword mesonBuiltin \ add_project_link_arguments \ add_test_setup \ benchmark + \ both_libraries \ build_machine \ build_target \ configuration_data diff --git a/docs/markdown/Adding-new-projects-to-wrapdb.md b/docs/markdown/Adding-new-projects-to-wrapdb.md index 4420de5..58b27ba 100644 --- a/docs/markdown/Adding-new-projects-to-wrapdb.md +++ b/docs/markdown/Adding-new-projects-to-wrapdb.md @@ -37,11 +37,10 @@ Each project gets its own repo. It is initialized like this: git init git add readme.txt - git commit -a -m 'Start of project foobar.' - git tag commit_zero -a -m 'A tag that helps get revision ids for releases.' + git add LICENSE.build + git commit -a -m 'Create project foobar' git remote add origin <repo url> git push -u origin master - git push --tags Note that this is the *only* commit that will ever be made to master branch. All other commits are done to branches. diff --git a/docs/markdown/Compiler-properties.md b/docs/markdown/Compiler-properties.md index 579417a..1228f42 100644 --- a/docs/markdown/Compiler-properties.md +++ b/docs/markdown/Compiler-properties.md @@ -160,15 +160,30 @@ Does a function exist? Just having a header doesn't say anything about its contents. Sometimes you need to explicitly check if some function -exists. This is how we would check whether the function `somefunc` -exists in header `someheader.h` +exists. This is how we would check whether the function `open_memstream` +exists in header `stdio.h` ```meson -if compiler.has_function('somefunc', prefix : '#include<someheader.h>') +if compiler.has_function('open_memstream', prefix : '#include <stdio.h>') # function exists, do whatever is required. endif ``` +Note that, on macOS programs can be compiled targeting older macOS +versions than the one that the program is compiled on. It can't be +assumed that the OS version that is compiled on matches the OS +version that the binary will run on. + +Therefore when detecting function availability with `has_function`, it +is important to specify the correct header in the prefix argument. + +In the example above, the function `open_memstream` is detected, which +was introduced in macOS 10.13. When the user builds on macOS 10.13, but +targeting macOS 10.11 (`-mmacosx-version-min=10.11`), this will correctly +report the function as missing. Without the header however, it would lack +the necessary availability information and incorrectly report the function +as available. + Does a structure contain a member? == diff --git a/docs/markdown/Cross-compilation.md b/docs/markdown/Cross-compilation.md index e739e37..7d316ed 100644 --- a/docs/markdown/Cross-compilation.md +++ b/docs/markdown/Cross-compilation.md @@ -10,17 +10,23 @@ nomenclature. The three most important definitions are traditionally called *build*, *host* and *target*. This is confusing because those terms are used for quite many different things. To simplify the issue, we are going to call these the *build machine*, *host machine* and -*target machine*. Their definitions are the following +*target machine*. Their definitions are the following: -* *build machine* is the computer that is doing the actual compiling -* *host machine* is the machine on which the compiled binary will run -* *target machine* is the machine on which the compiled binary's output will run (this is only meaningful for programs such as compilers that, when run, produce object code for a different CPU than what the program is being run on) +* *build machine* is the computer that is doing the actual compiling. +* *host machine* is the machine on which the compiled binary will run. +* *target machine* is the machine on which the compiled binary's + output will run, *only meaningful* if the program produces + machine-specific output. The `tl/dr` summary is the following: if you are doing regular cross -compilation, you only care about *build_machine* and -*host_machine*. Just ignore *target_machine* altogether and you will -be correct 99% of the time. If your needs are more complex or you are -interested in the actual details, do read on. +compilation, you only care about `build_machine` and +`host_machine`. Just ignore `target_machine` altogether and you will +be correct 99% of the time. Only compilers and similar tools care +about the target machine. In fact, for so-called "multi-target" tools +the target machine need not be fixed at build-time like the others but +chosen at runtime, so `target_machine` *still* doesn't matter. If your +needs are more complex or you are interested in the actual details, do +read on. This might be easier to understand through examples. Let's start with the regular, not cross-compiling case. In these cases all of these @@ -50,6 +56,20 @@ Wikipedia or the net in general. It is very common for them to get build, host and target mixed up, even in consecutive sentences, which can leave you puzzled until you figure it out. +A lot of confusion stems from the fact that when you cross-compile +something, the 3 systems (*build*, *host*, and *target*) used when +building the cross compiler don't align with the ones used when +building something with that newly-built cross compiler. To take our +Canadian Cross scenario from above (for full generality), since its +*host machine* is x86 Windows, the *build machine* of anything we +build with it is *x86 Windows*. And since its *target machine* is MIPS +Linux, the *host machine* of anything we build with it is *MIPS +Linux*. Only the *target machine* of whatever we build with it can be +freely chosen by us, say if we want to build another cross compiler +that runs on MIPS Linux and targets Aarch64 iOS. As this example +hopefully makes clear to you, the platforms are shifted over to the +left by one position. + If you did not understand all of the details, don't worry. For most people it takes a while to wrap their head around these concepts. Don't panic, it might take a while to click, but you will @@ -82,8 +102,9 @@ of a wrapper, these lines are all you need to write. Meson will automatically use the given wrapper when it needs to run host binaries. This happens e.g. when running the project's test suite. -The next section lists properties of the cross compiler and thus of -the host system. It looks like this: +The next section lists properties of the cross compiler and its target +system, and thus properties of host system of what we're building. It +looks like this: ```ini [properties] diff --git a/docs/markdown/FAQ.md b/docs/markdown/FAQ.md index f4cf89b..ff93216 100644 --- a/docs/markdown/FAQ.md +++ b/docs/markdown/FAQ.md @@ -288,3 +288,46 @@ has a option called `wrap-mode` which can be used to disable wrap downloads altogether with `--wrap-mode=nodownload`. You can also disable dependency fallbacks altogether with `--wrap-mode=nofallback`, which also implies the `nodownload` option. + +If on the other hand, you want meson to always use the fallback +for dependencies, even when an external dependency exists and could +satisfy the version requirements, for example in order to make +sure your project builds when fallbacks are used, you can use +`--wrap-mode=forcefallback` since 0.46.0. + +## Why is Meson implemented in Python rather than [programming language X]? + +Because build systems are special in ways normal applications aren't. + +Perhaps the biggest limitation is that because Meson is used to build +software at the very lowest levels of the OS, it is part of the core +bootstrap for new systems. Whenever support for a new CPU architecture +is added, Meson must run on the system before software using it can be +compiled natively. This requirement adds two hard limitations. + +The first one is that Meson must have the minimal amount of +dependencies, because they must all be built during the bootstrap to +get Meson to work. + +The second is that Meson must support all CPU architectures, both +existing and future ones. As an example many new programming languages +have only an LLVM based compiler available. LLVM has limited CPU +support compared to, say, GCC, and thus bootstrapping Meson on such +platforms would first require adding new processor support to +LLVM. This is in most cases unfeasible. + +A further limitation is that we want developers on as many platforms +as possible to submit to Meson development using the default tools +provided by their operating system. In practice what this means is +that Windows developers should be able to contribute using nothing but +Visual Studio. + +At the time of writing (April 2018) there are only three languages +that could fullfill these requirements: + + - C + - C++ + - Python + +Out of these we have chosen Python because it is the best fit for our +needs. diff --git a/docs/markdown/Gnome-module.md b/docs/markdown/Gnome-module.md index ad3715e..3db6cc0 100644 --- a/docs/markdown/Gnome-module.md +++ b/docs/markdown/Gnome-module.md @@ -235,9 +235,21 @@ files and the second specifies the XML file name. * `object_manager`: *(Added 0.40.0)* if true generates object manager code * `annotations`: *(Added 0.43.0)* list of lists of 3 strings for the annotation for `'ELEMENT', 'KEY', 'VALUE'` * `docbook`: *(Added 0.43.0)* prefix to generate `'PREFIX'-NAME.xml` docbooks +* `build_by_default`: causes, when set to true, to have this target be + built by default, that is, when invoking plain `ninja`, the default + value is true for all built target types +* `install_dir`: (*Added 0.46.0*) location to install the header or + bundle depending on previous options +* `install_header`: (*Added 0.46.0*) if true, install the header file + +Starting *0.46.0*, this function returns a list of at least two custom targets +(in order): one for the source code and one for the header. The list will +contain a third custom target for the generated docbook files if that keyword +argument is passed. -Returns an opaque object containing the source files. Add it to a top -level target's source list. +Earlier versions return a single custom target representing all the outputs. +Generally, you should just add this list of targets to a top level target's +source list. Example: diff --git a/docs/markdown/Pkg-config-files.md b/docs/markdown/Pkg-config-files.md index dde4ac9..ddb8bab 100644 --- a/docs/markdown/Pkg-config-files.md +++ b/docs/markdown/Pkg-config-files.md @@ -1,6 +1,6 @@ # Pkg config files -[Pkg-config](https://en.wikipedia.org/wiki/Pkg-config) is a way for shared libraries to declare the compiler flags needed to use them. There are two different ways of generating Pkg-config files in Meson. The first way is to build them manually with the `configure_files` command. The second way is to use Meson's built in Pkg-config file generator. The difference between the two is that the latter is very simple and meant for basic use cases. The former should be used when you need to provide a more customized solution. +[Pkg-config](https://en.wikipedia.org/wiki/Pkg-config) is a way for shared libraries to declare the compiler flags needed to use them. There are two different ways of generating Pkg-config files in Meson. The first way is to build them manually with the `configure_file` command. The second way is to use Meson's built in Pkg-config file generator. The difference between the two is that the latter is very simple and meant for basic use cases. The former should be used when you need to provide a more customized solution. In this document we describe the simple generator approach. It is used in the following way. diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index 853cf50..77db809 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -51,3 +51,10 @@ keyword arguments. - `version` a string describing the version of this library - `d_module_versions` a list of module version flags used when compiling D sources referred to by this pkg-config file + +Since 0.46 a `StaticLibrary` or `SharedLibrary` object can optionally be passed +as first positional argument. If one is provided a default value will be +provided for all required fields of the pc file: +- `install_dir` is set to `pkgconfig` folder in the same location than the provided library. +- `description` is set to the project's name followed by the library's name. +- `name` is set to the library's name. diff --git a/docs/markdown/Python-module.md b/docs/markdown/Python-module.md new file mode 100644 index 0000000..cad74c9 --- /dev/null +++ b/docs/markdown/Python-module.md @@ -0,0 +1,195 @@ +--- +short-description: Generic python module +authors: + - name: Mathieu Duponchelle + email: mathieu@centricular.com + years: [2018] + has-copyright: false +... + +# Python module + +This module provides support for finding and building extensions against +python installations, be they python 2 or 3. + +*Added 0.46.0* + +## Functions + +### `find_installation()` + +``` meson +pymod.find_installation(name_or_path, ...) +``` + +Find a python installation matching `name_or_path`. + +That argument is optional, if not provided then the returned python +installation will be the one used to run meson. + +If provided, it can be: + +- A simple name, eg `python-2.7`, meson will look for an external program + named that way, using [find_program] + +- A path, eg `/usr/local/bin/python3.4m` + +- One of `python2` or `python3`: in either case, the module will try some + alternative names: `py -2` or `py -3` on Windows, and `python` everywhere. + In the latter case, it will check whether the version provided by the + sysconfig module matches the required major version + +Keyword arguments are the following: + +- `required`: by default, `required` is set to `true` and Meson will + abort if no python installation can be found. If `required` is set to `false`, + Meson will continue even if no python installation was found. You can + then use the `.found()` method on the returned object to check + whether it was found or not. + +**Returns**: a [python installation][`python_installation` object] + +## `python_installation` object + +The `python_installation` object is an [external program], with several +added methods. + +### Methods + +#### `extension_module()` + +``` meson +shared_module py_installation.extension_module(module_name, list_of_sources, ...) +``` + +Create a `shared_module` target that is named according to the naming +conventions of the target platform. + +All positional and keyword arguments are the same as for [shared_module], +excluding `name_suffix` and `name_prefix`, and with the addition of the following: + +- `subdir`: By default, meson will install the extension module in + the relevant top-level location for the python installation, eg + `/usr/lib/site-packages`. When subdir is passed to this method, + it will be appended to that location. This keyword argument is + mutually exclusive with `install_dir` + +`extension_module` does not add any dependencies to the library so user may +need to add `dependencies : py_installation.dependency()`, see [][`dependency()`]. + +**Returns**: a [buildtarget object] + +#### `dependency()` + +``` meson +python_dependency py_installation.dependency(...) +``` + +This method accepts the same arguments as the standard [dependency] function. + +**Returns**: a [python dependency][`python_dependency` object] + +#### `install_sources()` + +``` meson +void py_installation.install_sources(list_of_files, ...) +``` + +Install actual python sources (`.py`). + +All positional and keyword arguments are the same as for [install_data], +with the addition of the following: + +- `pure`: On some platforms, architecture independent files are expected + to be placed in a separate directory. However, if the python sources + should be installed alongside an extension module built with this + module, this keyword argument can be used to override that behaviour. + Defaults to `true` + +- `subdir`: See documentation for the argument of the same name to + [][`extension_module()`] + +#### `get_install_dir()` + +``` meson +string py_installation.get_install_dir(...) +``` + +Retrieve the directory [][`install_sources()`] will install to. + +It can be useful in cases where `install_sources` cannot be used directly, +for example when using [configure_file]. + +This function accepts no arguments, its keyword arguments are the same +as [][`install_sources()`]. + +**Returns**: A string + +#### `language_version()` + +``` meson +string py_installation.language_version() +``` + +Get the major.minor python version, eg `2.7`. + +The version is obtained through the `sysconfig` module. + +This function expects no arguments or keyword arguments. + +**Returns**: A string + +#### `get_path()` + +``` meson +string py_installation.get_path(path_name) +``` + +Get a path as defined by the `sysconfig` module. + +For example: + +``` meson +purelib = py_installation.get_path('purelib') +``` + +This function accepts a single argument, `path_name`, which is expected to +be a non-empty string. + +**Returns**: A string + +#### `get_variable()` + +``` meson +string py_installation.get_variable(variable_name) +``` + +Get a variable as defined by the `sysconfig` module. + +For example: + +``` meson +py_bindir = py_installation.get_variable('BINDIR') +``` + +This function accepts a single argument, `variable_name`, which is expected to +be a non-empty string. + +**Returns**: A string + +## `python_dependency` object + +This [dependency object] subclass will try various methods to obtain the +compiler and linker arguments, starting with pkg-config then potentially +using information obtained from python's `sysconfig` module. + +It exposes the same methods as its parent class. + +[find_program]: Reference-manual.md#find_program +[shared_module]: Reference-manual.md#shared_module +[external program]: Reference-manual.md#external-program-object +[dependency]: Reference-manual.md#dependency +[install_data]: Reference-manual.md#install-data +[configure_file]: Reference-manual.md#configure-file +[dependency object]: Reference-manual.md#dependency-object +[buildtarget object]: Reference-manual.md#build-target-object diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index e652b64..32639b0 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -112,6 +112,24 @@ run. The behavior of this function is identical to `test` with the exception that there is no `is_parallel` keyword, because benchmarks are never run in parallel. +### both_libraries() + +``` meson + buildtarget both_libraries(library_name, list_of_sources, ...) +``` + +Builds both a static and shared library with the given sources. Positional and +keyword arguments are otherwise the same as for [`library`](#library). Source +files will be compiled only once and object files will be reused to build both +shared and static libraries, unless `b_staticpic` user option or `pic` argument +are set to false in which case sources will be compiled twice. + +The returned [buildtarget](#build-target-object) always represents the shared +library. In addition it supports the following extra methods: + +- `get_shared_lib()` returns the shared library build target +- `get_static_lib()` returns the static library build target + ### build_target() Creates a build target whose type can be set dynamically with the @@ -168,7 +186,12 @@ These are all the supported keyword arguments: `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 + you to do fully custom file generation. +- `format` *(added 0.46.0)* the format of defines. It defaults to `meson`, and so substitutes +`#mesondefine` statements and variables surrounded by `@` characters, you can also use `cmake` +to replace `#cmakedefine` statements and variables with the `${variable}` syntax. Finally you can use +`cmake@` in which case substitutions will apply on `#cmakedefine` statements and variables with +the `@variable@` syntax. - `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. @@ -885,10 +908,11 @@ dropped. That means that `join_paths('foo', '/bar')` returns `/bar`. buildtarget library(library_name, list_of_sources, ...) ``` -Builds a library that is either static or shared depending on the -value of `default_library` user option. You should use this instead of -[`shared_library`](#shared_library) or -[`static_library`](#static_library) most of the time. This allows you +Builds a library that is either static, shared or both depending on the value of +`default_library` user option. You should use this instead of +[`shared_library`](#shared_library), +[`static_library`](#static_library) or +[`both_libraries`](#both_libraries) most of the time. This allows you to toggle your entire project (including subprojects) from shared to static with only one option. @@ -911,7 +935,8 @@ The keyword arguments for this are the same as for [`executable`](#executable) w libraries. Defaults to `dylib` for shared libraries and `rlib` for static libraries. -`static_library` and `shared_library` also accept these keyword arguments. +`static_library`, `shared_library` and `both_libraries` also accept these keyword +arguments. ### message() @@ -1129,6 +1154,33 @@ This function has one keyword argument. recurse in the subdir if they all return `true` when queried with `.found()` +### subdir_done() + +``` meson + subdir_done() +``` + +Stops further interpretation of the meson script file from the point of +the invocation. All steps executed up to this point are valid and will +be executed by meson. This means that all targets defined before the call +of `subdir_done` will be build. + +If the current script was called by `subdir` the execution returns to the +calling directory and continues as if the script had reached the end. +If the current script is the top level script meson configures the project +as defined up to this point. + +Example: +```meson +project('example exit', 'cpp') +executable('exe1', 'exe1.cpp') +subdir_done() +executable('exe2', 'exe2.cpp') +``` + +The executable `exe1` will be build, while the executable `exe2` is not +build. + ### subproject() ``` meson @@ -1191,6 +1243,12 @@ Keyword arguments are the following: - `should_fail` when true the test is considered passed if the executable returns a non-zero return value (i.e. reports an error) +- `suite` `'label'` (or list of labels `['label1', 'label2']`) + attached to this test. The suite name is qualified by a (sub)project + name resulting in `(sub)project_name:label`. In the case of a list + of strings, the suite names will be `(sub)project_name:label1`, + `(sub)project_name:label2`, etc. + - `timeout` the amount of seconds the test is allowed to run, a test that exceeds its time limit is always considered failed, defaults to 30 seconds @@ -1345,6 +1403,13 @@ the following methods. /path/to/meson.py introspect`. The user is responsible for splitting the string to an array if needed. +- `override_find_program(progname, program)` [*(Added + 0.46.0)*](Release-notes-for-0-46-0.html#Can-override-find_program) + specifies that whenever `find_program` is used to find a program + named `progname`, Meson should not not look it up on the system but + instead return `program`, which may either be the result of + `find_program` or `configure_file`. + - `project_version()` returns the version string specified in `project` function call. - `project_license()` returns the array of licenses specified in `project` function call. @@ -1450,7 +1515,11 @@ the following methods: - `first_supported_argument(list_of_strings)`, given a list of strings, returns the first argument that passes the `has_argument` - test above or an empty array if none pass. + test or an empty array if none pass. + +- `first_supported_link_argument(list_of_strings)` *(added 0.46.0)*, given a + list of strings, returns the first argument that passes the + `has_link_argument` test or an empty array if none pass. - `get_define(definename)` returns the given preprocessor symbol's value as a string or empty string if it is not defined. @@ -1462,11 +1531,19 @@ the following methods: an array containing only the arguments supported by the compiler, as if `has_argument` were called on them individually. +- `get_supported_link_arguments(list_of_string)` *(added 0.46.0)* returns + an array containing only the arguments supported by the linker, + as if `has_link_argument` were called on them individually. + - `has_argument(argument_name)` returns true if the compiler accepts the specified command line argument, that is, can compile code - without erroring out or printing a warning about an unknown flag, - you can specify external dependencies to use with `dependencies` - keyword argument. + without erroring out or printing a warning about an unknown flag. + +- `has_link_argument(argument_name)` *(added 0.46.0)* returns true if the linker + accepts the specified command line argument, that is, can compile and link + code without erroring out or printing a warning about an unknown flag. Link + arguments will be passed to the compiler, so should usually have the `-Wl,` + prefix. On VisualStudio a `/link` argument will be prepended. - `has_function(funcname)` returns true if the given function is provided by the standard library or a library passed in with the @@ -1501,6 +1578,10 @@ the following methods: `has_argument` but takes multiple arguments and uses them all in a single compiler invocation, available since 0.37.0. +- `has_multi_link_arguments(arg1, arg2, arg3, ...)` *(added 0.46.0)* is the same + as `has_link_argument` but takes multiple arguments and uses them all in a + single compiler invocation. + - `has_type(typename)` returns true if the specified token is a type, you can specify external dependencies to use with `dependencies` keyword argument. @@ -1650,7 +1731,8 @@ These are objects returned by the [functions listed above](#functions). ### `build target` object A build target is either an [executable](#executable), -[shared](#shared_library), [static library](#static_library) or +[shared library](#shared_library), [static library](#static_library), +[both shared and static library](#both_libraries) or [shared module](#shared_module). - `extract_all_objects()` is same as `extract_objects` but returns all @@ -1752,6 +1834,32 @@ an external dependency with the following methods: - `version()` is the version number as a string, for example `1.2.8` + - `partial_dependency(compile_args : false, link_args : false, links : false, + includes : false, source : false)` (*added 0.46.0) returns a new dependency + object with the same name, version, found status, type name, and methods as + the object that called it. This new object will only inherit other + attributes from its parent as controlled by keyword arguments. + + If the parent has any dependencies, those will be applied to the new + partial dependency with the same rules. So , given: + + ```meson + dep1 = declare_dependency(compiler_args : '-Werror=foo', link_with : 'libfoo') + dep2 = declare_dependency(compiler_args : '-Werror=bar', dependencies : dep1) + dep3 = dep2.partial_dependency(compile_args : true) + ``` + + dep3 will add `['-Werror=foo', '-Werror=bar']` to the compiler args of + any target it is added to, but libfoo will not be added to the link_args. + + The following arguments will add the following attributes: + + - compile_args: any arguments passed to the compiler + - link_args: any arguments passed to the linker + - links: anything passed via link_with or link_whole + - includes: any include_directories + - sources: any compiled or static sources the dependency has + ### `disabler` object A disabler object is an object that behaves in much the same way as diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md index 7611232..2157e72 100644 --- a/docs/markdown/Reference-tables.md +++ b/docs/markdown/Reference-tables.md @@ -21,6 +21,8 @@ These are return values of the `get_id` method in a compiler object. | g95 | The G95 Fortran compiler | | open64 | The Open64 Fortran Compiler | | nagfor | The NAG Fortran compiler | +| lcc | Elbrus C/C++/Fortran Compiler | +| arm | ARM compiler | ## Script environment variables @@ -42,6 +44,7 @@ set in the cross file. | x86 | 32 bit x86 processor | | x86_64 | 64 bit x86 processor | | arm | 32 bit ARM processor | +| e2k | MCST Elbrus processor | Any cpu family not listed in the above list is not guaranteed to remain stable in future releases. diff --git a/docs/markdown/Release-notes-for-0.45.0.md b/docs/markdown/Release-notes-for-0.45.0.md index 6b24183..19d65b8 100644 --- a/docs/markdown/Release-notes-for-0.45.0.md +++ b/docs/markdown/Release-notes-for-0.45.0.md @@ -18,7 +18,7 @@ ldflags, etc) from. These binaries may now be specified in the `binaries` section of a cross file. -```dosini +```ini [binaries] cc = ... llvm-config = '/usr/bin/llvm-config32' @@ -37,12 +37,16 @@ time. Starting with this version it becomes a hard error. There used to be a keywordless version of `run_target` which looked like this: - run_target('targetname', 'command', 'arg1', 'arg2') +```meson +run_target('targetname', 'command', 'arg1', 'arg2') +``` This is now an error. The correct format for this is now: - run_target('targetname', - command : ['command', 'arg1', 'arg2']) +```meson +run_target('targetname', + command : ['command', 'arg1', 'arg2']) +``` ## Experimental FPGA support @@ -84,7 +88,9 @@ private directory: Hexadecimal integer literals can now be used in build and option files. - int_255 = 0xFF +```meson +int_255 = 0xFF +``` ## b_ndebug : if-release @@ -110,7 +116,9 @@ instead of directory itself, stripping basename of the source directory. There is a new integer option type with optional minimum and maximum values. It can be specified like this in the `meson_options.txt` file: - option('integer_option', type : 'integer', min : 0, max : 5, value : 3) +```meson +option('integer_option', type : 'integer', min : 0, max : 5, value : 3) +``` ## New method meson.project_license() @@ -124,7 +132,7 @@ cross-compilers, the Rust binary must be specified in your cross file. It should specify a `--target` (as installed by `rustup target`) and a custom linker pointing to your C cross-compiler. For example: -``` +```ini [binaries] c = '/usr/bin/arm-linux-gnueabihf-gcc-7' rust = [ @@ -146,9 +154,7 @@ private sysroot. Meson ships with predefined project templates. To start a new project from scratch, simply go to an empty directory and type: -```meson -meson init --name=myproject --type=executable --language=c -``` + meson init --name=myproject --type=executable --language=c ## Improve test setup selection diff --git a/docs/markdown/Release-notes-for-0.46.0.md b/docs/markdown/Release-notes-for-0.46.0.md index 395a94d..e062459 100644 --- a/docs/markdown/Release-notes-for-0.46.0.md +++ b/docs/markdown/Release-notes-for-0.46.0.md @@ -14,3 +14,10 @@ whose contents should look like this: ## Feature name A short description explaining the new feature and how it should be used. + +## Allow early return from a script + +Added the function `subdir_done()`. Its invocation exits the current script at +the point of invocation. All previously invoked build targets and commands are +build/executed. All following ones are ignored. If the current script was +invoked via `subdir()` the parent script continues normally. diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md index 1005100..01c8c6e 100644 --- a/docs/markdown/Syntax.md +++ b/docs/markdown/Syntax.md @@ -90,8 +90,24 @@ single quote do it like this: single quote = 'contains a \' character' ``` -Similarly `\n` gets converted to a newline and `\\` to a single -backslash. +The full list of escape sequences is: + +* `\\` Backslash +* `\'` Single quote +* `\a` Bell +* `\b` Backspace +* `\f` Formfeed +* `\n` Newline +* `\r` Carriage Return +* `\t` Horizontal Tab +* `\v` Vertical Tab +* `\ooo` Character with octal value ooo +* `\xhh` Character with hex value hh +* `\uxxxx` Character with 16-bit hex value xxxx +* `\Uxxxxxxxx` Character with 32-bit hex value xxxxxxxx +* `\N{name}` Character named name in Unicode database + +As in python and C, up to three octal digits are accepted in `\ooo`. #### String concatenation diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index 53ce9ec..e5e4107 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -71,6 +71,14 @@ You can also run only a single test by giving its name: $ meson test testname ``` +Tests belonging to a suite `suite` can be run as follows + +```console +$ meson test --suite (sub)project_name:suite +``` + +Since version *0.46*, `(sub)project_name` can be omitted if it is the top-level project. + Sometimes you need to run the tests multiple times, which is done like this: ```console diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md index e152555..558378c 100644 --- a/docs/markdown/Users.md +++ b/docs/markdown/Users.md @@ -4,7 +4,8 @@ title: Users # List of projects using Meson -If you have a project that uses Meson that you want to add to this list, please [file a pull-request](https://github.com/mesonbuild/meson/edit/master/docs/markdown/Users.md) for it. All the software on this list is tested for regressions before release, so it's highly recommended that projects add themselves here. +If you have a project that uses Meson that you want to add to this list, please [file a pull-request](https://github.com/mesonbuild/meson/edit/master/docs/markdown/Users.md) for it. All the software on this list is tested for regressions before release, so it's highly recommended that projects add themselves here. Some additional projects are +listed in the [`meson` GitHub topic](https://github.com/topics/meson). - [AQEMU](https://github.com/tobimensch/aqemu), a Qt GUI for QEMU virtual machines, since version 0.9.3 - [Arduino sample project](https://github.com/jpakkane/mesonarduino) diff --git a/docs/markdown/snippets/armcc-cross.md b/docs/markdown/snippets/armcc-cross.md new file mode 100644 index 0000000..668f0ab --- /dev/null +++ b/docs/markdown/snippets/armcc-cross.md @@ -0,0 +1,15 @@ +## ARM compiler for C and CPP + +Cross-compilation is now supported for ARM targets using ARM compiler - ARMCC. +The current implementation does not support shareable libraries. +The default extension of the output is .axf. +The environment path should be set properly for the ARM compiler executables. +The '--cpu' option with the appropriate target type should be mentioned +in the cross file as shown in the snippet below. + +``` +[properties] +c_args = ['--cpu=Cortex-M0plus'] +cpp_args = ['--cpu=Cortex-M0plus'] + +``` diff --git a/docs/markdown/snippets/both-libraries.md b/docs/markdown/snippets/both-libraries.md new file mode 100644 index 0000000..1632f63 --- /dev/null +++ b/docs/markdown/snippets/both-libraries.md @@ -0,0 +1,9 @@ +## Building both shared and static libraries + +A new function `both_libraries()` has been added to build both shared and static +libraries at the same time. Source files will be compiled only once and object +files will be reused to build both shared and static libraries, unless +`b_staticpic` user option or `pic` argument are set to false in which case +sources will be compiled twice. + +The returned `buildtarget` object always represents the shared library. diff --git a/docs/markdown/snippets/d-options-for-meson-setup.md b/docs/markdown/snippets/d-options-for-meson-setup.md new file mode 100644 index 0000000..37afbe0 --- /dev/null +++ b/docs/markdown/snippets/d-options-for-meson-setup.md @@ -0,0 +1,6 @@ +## Meson and meson configure now accept the same arguments + +Previously meson required that builtin arguments (like prefix) be passed as +`--prefix` to `meson` and `-Dprefix` to `meson configure`. `meson` now accepts -D +form like `meson configure` has. `meson configure` also accepts the `--prefix` +form, like `meson` has. diff --git a/docs/markdown/snippets/del-old-names.md b/docs/markdown/snippets/del-old-names.md index c4abc9a..5ac5873 100644 --- a/docs/markdown/snippets/del-old-names.md +++ b/docs/markdown/snippets/del-old-names.md @@ -2,6 +2,6 @@ Old executable names `mesonintrospect`, `mesonconf`, `mesonrewriter` and `mesontest` have been deprecated for a long time. Starting from -this versino they no longer do anything but instead always error +this version they no longer do anything but instead always error out. All functionality is available as subcommands in the main `meson` binary. diff --git a/docs/markdown/snippets/find-override.md b/docs/markdown/snippets/find-override.md new file mode 100644 index 0000000..ef3a4a2 --- /dev/null +++ b/docs/markdown/snippets/find-override.md @@ -0,0 +1,37 @@ +## Can override find_program + +It is now possible to override the result of `find_program` to point +to a custom program you want. The overriding is global and applies to +every subproject from there on. Here is how you would use it. + +In master project + +```meson +subproject('mydep') +``` + +In the called subproject: + +```meson +prog = find_program('my_custom_script') +meson.override_find_program('mycodegen', prog) +``` + +In master project (or, in fact, any subproject): + +```meson +genprog = find_program('mycodegen') +``` + +Now `genprog` points to the custom script. If the dependency had come +from the system, then it would point to the system version. + +You can also use the return value of `configure_file()` to override +a program in the same way as above: + +```meson +prog_script = configure_file(input : 'script.sh.in', + output : 'script.sh', + configuration : cdata) +meson.override_find_program('mycodegen', prog_script) +``` diff --git a/docs/markdown/snippets/has-link-argument.md b/docs/markdown/snippets/has-link-argument.md new file mode 100644 index 0000000..7beda63 --- /dev/null +++ b/docs/markdown/snippets/has-link-argument.md @@ -0,0 +1,9 @@ +## has_link_argument() and friends + +A new set of methods has been added on compiler objects to test if the linker +supports given arguments. + +- `has_link_argument()` +- `has_multi_link_arguments()` +- `get_supported_link_arguments()` +- `first_supported_link_argument()` diff --git a/docs/markdown/snippets/lcc.md b/docs/markdown/snippets/lcc.md new file mode 100644 index 0000000..2ce300d --- /dev/null +++ b/docs/markdown/snippets/lcc.md @@ -0,0 +1,23 @@ +## Support for lcc compiler for e2k (Elbrus) architecture + +In this version, a support for lcc compiler for Elbrus processors +based on [e2k microarchitecture](https://en.wikipedia.org/wiki/Elbrus_2000) +has been added. + +Examples of such CPUs: +* [Elbrus-8S](https://en.wikipedia.org/wiki/Elbrus-8S); +* Elbrus-4S; +* [Elbrus-2S+](https://en.wikipedia.org/wiki/Elbrus-2S%2B). + +Such compiler have a similar behavior as gcc (basic option compatibility), +but, in is not strictly compatible with gcc as of current version. + +Major differences as of version 1.21.22: +* it does not support LTO and PCH; +* it suffers from the same dependency file creation error as icc; +* it has minor differences in output, especially version output; +* it differently reacts to lchmod() detection; +* some backend messages are produced in ru_RU.KOI8-R even if LANG=C; +* its preprocessor treats some characters differently. + +So every noted difference is properly handled now in meson.
\ No newline at end of file diff --git a/docs/markdown/snippets/more-escape-sequences.md b/docs/markdown/snippets/more-escape-sequences.md new file mode 100644 index 0000000..2894079 --- /dev/null +++ b/docs/markdown/snippets/more-escape-sequences.md @@ -0,0 +1,17 @@ +## String escape character update + +The strings (both single-quoted and triple-quoted) in meson has been taught the +same set of escape sequences as in Python. It is therefore now possible to use +arbitrary bytes in strings, like for example NUL (`\0`) and other ASCII control +characters. See the chapter about *Strings* in *Syntax* for more details. + +Potential backwards compatibility issue: Any valid escape sequence according to +the new rules will be interpreted as an escape sequence instead of the literal +characters. Previously only single-quote strings supported escape sequences and +the supported sequences were `\'`, `\\` and `\n`. + +The most likely breakage is usage of backslash-n in triple-quoted strings. It +is now written in the same way as in single-quoted strings: `\\n` instead of +`\n`. In general it is now recommended to escape any usage of backslash. +However, backslash-c (`\c`), for example, is still backslash-c because it isn't +a valid escape sequence. diff --git a/docs/markdown/snippets/new-wrap-mode.md b/docs/markdown/snippets/new-wrap-mode.md new file mode 100644 index 0000000..e33dd83 --- /dev/null +++ b/docs/markdown/snippets/new-wrap-mode.md @@ -0,0 +1,3 @@ +A new wrap mode was added, `--wrap-mode=forcefallback`. When this is set, +dependencies for which a fallback was provided will always use it, even +if an external dependency exists and satisfies the version requirements. diff --git a/docs/markdown/snippets/non-unique-target-names.md b/docs/markdown/snippets/non-unique-target-names.md new file mode 100644 index 0000000..9b3f917 --- /dev/null +++ b/docs/markdown/snippets/non-unique-target-names.md @@ -0,0 +1,9 @@ +## Relaxing of target name requirements + +In earlier versions of Meson you could only have one target of a given name for each type. +For example you could not have two executables named `foo`. This requirement is now +relaxed so that you can have multiple targets with the same name, as long as they are in +different subdirectories. + +Note that projects that have multiple targets with the same name can not be built with +the `flat` layout or any backend that writes outputs in the same directory. diff --git a/docs/markdown/snippets/openmp-dependency.md b/docs/markdown/snippets/openmp-dependency.md new file mode 100644 index 0000000..ad70011 --- /dev/null +++ b/docs/markdown/snippets/openmp-dependency.md @@ -0,0 +1,6 @@ +## Addition of OpenMP dependency + +An OpenMP dependency (`openmp`) has been added that encapsulates the various +flags used by compilers to enable OpenMP and checks for the existence of the +`omp.h` header. The `language` keyword may be passed to force the use of a +specific compiler for the checks. diff --git a/docs/markdown/snippets/partial-dependencies.md b/docs/markdown/snippets/partial-dependencies.md new file mode 100644 index 0000000..8d2136a --- /dev/null +++ b/docs/markdown/snippets/partial-dependencies.md @@ -0,0 +1,25 @@ +## Added new partial_dependency method to dependencies and libraries + +It is now possible to use only part of a dependency in a target. This allows, +for example, to only use headers with convenience libraries to avoid linking +to the same library multiple times. + +```meson + +dep = dependency('xcb') + +helper = static_library( + 'helper', + ['helper1.c', 'helper2.c'], + dependencies : dep.partial_dependency(includes : true), +] + +final = shared_library( + 'final', + ['final.c'], + dependencyes : dep, +) +``` + +A partial dependency will have the same name version as the full dependency it +is derived from, as well as any values requested. diff --git a/docs/markdown/snippets/pkg-config-fix-static-only.md b/docs/markdown/snippets/pkg-config-fix-static-only.md new file mode 100644 index 0000000..31cd389 --- /dev/null +++ b/docs/markdown/snippets/pkg-config-fix-static-only.md @@ -0,0 +1,12 @@ +## Improved generation of pkg-config files for static only libraries. + +Previously pkg-config files generated by the pkgconfig modules for static libraries +with dependencies could only be used in a dependencies with `static: true`. + +Now the generated file contains the needed dependencies libraries directly within +`Requires` and `Libs` for build static libraries passed via the `libraries` keyword +argument. + +Projects that install both a static and a shared version of a library should use +the result of `both_libraries` to the pkg config file generator or use +configure_file for more complicated setups. diff --git a/docs/markdown/snippets/pkgconfig-generator.md b/docs/markdown/snippets/pkgconfig-generator.md new file mode 100644 index 0000000..93920d3 --- /dev/null +++ b/docs/markdown/snippets/pkgconfig-generator.md @@ -0,0 +1,14 @@ +## Improvements to pkgconfig module + +A `StaticLibrary` or `SharedLibrary` object can optionally be passed +as first positional argument of the `generate()` method. If one is provided a +default value will be provided for all required fields of the pc file: +- `install_dir` is set to `pkgconfig` folder in the same location than the provided library. +- `description` is set to the project's name followed by the library's name. +- `name` is set to the library's name. + +Generating a .pc file is now as simple as: + +``` +pkgconfig.generate(mylib) +``` diff --git a/docs/markdown/snippets/python-module.md b/docs/markdown/snippets/python-module.md new file mode 100644 index 0000000..c2e8138 --- /dev/null +++ b/docs/markdown/snippets/python-module.md @@ -0,0 +1,6 @@ +## Generic python module + +This is a revamped and generic (python 2 and 3) version of the python3 +module. With this new interface, projects can now fully specify the version +of python they want to build against / install sources to, and can do so +against multiple major or minor versions in parallel. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 844b600..b439b69 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -32,6 +32,7 @@ index.md i18n-module.md Icestorm-module.md Pkgconfig-module.md + Python-module.md Python-3-module.md Qt4-module.md Qt5-module.md diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html index 8f3da63..2edce24 100644 --- a/docs/theme/extra/templates/navbar_links.html +++ b/docs/theme/extra/templates/navbar_links.html @@ -8,6 +8,7 @@ @for tup in (("Gnome-module.html","GNOME"), \ ("i18n-module.html","i18n"), \ ("Pkgconfig-module.html","Pkgconfig"), \ + ("Python-module.html","Python"), \ ("Python-3-module.html","Python 3"), \ ("Qt4-module.html","Qt4"), \ ("Qt5-module.html","Qt5"), \ diff --git a/manual tests/2 multiwrap/meson.build b/manual tests/2 multiwrap/meson.build index 741a899..a4c42f4 100644 --- a/manual tests/2 multiwrap/meson.build +++ b/manual tests/2 multiwrap/meson.build @@ -6,7 +6,7 @@ project('multiwrap', 'c', cc = meson.get_compiler('c') luadep = dependency('lua', fallback : ['lua', 'lua_dep']) -pngdep = dependency('libpng', fallback : ['libpng', 'pngdep']) +pngdep = dependency('libpng', fallback : ['libpng', 'png_dep']) executable('prog', 'prog.c', dependencies : [pngdep, luadep]) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e0663b6..8f75dac 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -22,6 +22,7 @@ import json import subprocess from ..mesonlib import MesonException from ..mesonlib import get_compiler_for_source, classify_unity_sources +from ..mesonlib import File from ..compilers import CompilerArgs from collections import OrderedDict import shlex @@ -414,11 +415,20 @@ class Backend: objname = objname.replace('/', '_').replace('\\', '_') objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) return [objpath] - for osrc in extobj.srclist: + + sources = list(extobj.srclist) + for gensrc in extobj.genlist: + for s in gensrc.get_outputs(): + path = self.get_target_generated_dir(extobj.target, gensrc, s) + dirpart, fnamepart = os.path.split(path) + sources.append(File(True, dirpart, fnamepart)) + + for osrc in sources: objname = self.object_filename_from_source(extobj.target, osrc, False) if objname: objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) result.append(objpath) + return result def get_pch_include_args(self, compiler, target): @@ -530,6 +540,8 @@ class Backend: # pkg-config puts the thread flags itself via `Cflags:` if dep.need_threads(): commands += compiler.thread_flags(self.environment) + elif dep.need_openmp(): + commands += compiler.openmp_flags() # Fortran requires extra include directives. if compiler.language == 'fortran': for lt in target.link_targets: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 39e4ce9..bc3a8ef 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -169,7 +169,7 @@ class NinjaBackend(backends.Backend): break else: # None of our compilers are MSVC, we're done. - return open(tempfilename, 'a') + return open(tempfilename, 'a', encoding='utf-8') filename = os.path.join(self.environment.get_scratch_dir(), 'incdetect.c') with open(filename, 'w') as f: @@ -196,7 +196,7 @@ int dummy; if match: with open(tempfilename, 'ab') as binfile: binfile.write(b'msvc_deps_prefix = ' + match.group(1) + b'\n') - return open(tempfilename, 'a') + return open(tempfilename, 'a', encoding='utf-8') raise MesonException('Could not determine vs dep dependency prefix string.') def generate(self, interp): @@ -206,7 +206,7 @@ int dummy; raise MesonException('Could not detect Ninja v1.5 or newer') outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename) tempfilename = outfilename + '~' - with open(tempfilename, 'w') as outfile: + with open(tempfilename, 'w', encoding='utf-8') as outfile: outfile.write('# This is the build file for project "%s"\n' % self.build.get_project()) outfile.write('# It is autogenerated by the Meson build system.\n') @@ -613,13 +613,19 @@ int dummy; self.create_target_alias(target_name, outfile) self.processed_targets[target.get_id()] = True + def generate_coverage_command(self, elem, outputs): + elem.add_item('COMMAND', self.environment.get_build_command() + + ['--internal', 'coverage'] + + outputs + + [self.environment.get_source_dir(), + os.path.join(self.environment.get_source_dir(), + self.build.get_subproject_dir()), + self.environment.get_build_dir(), + self.environment.get_log_dir()]) + def generate_coverage_rules(self, outfile): e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY') - 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()]) + self.generate_coverage_command(e, []) e.add_item('description', 'Generates coverage reports.') e.write(outfile) # Alias that runs the target defined above @@ -627,80 +633,26 @@ int dummy; self.generate_coverage_legacy_rules(outfile) def generate_coverage_legacy_rules(self, outfile): - (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools() - added_rule = False - if gcovr_exe: - # gcovr >= 3.1 interprets rootdir differently - if gcovr_new_rootdir: - rootdir = self.environment.get_build_dir() - else: - rootdir = self.environment.get_source_dir(), - added_rule = True - elem = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', rootdir, - '-o', os.path.join(self.environment.get_log_dir(), 'coverage.xml')]) - elem.add_item('DESC', 'Generating XML coverage report.') - elem.write(outfile) - # Alias that runs the target defined above - self.create_target_alias('meson-coverage-xml', outfile) - elem = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-r', rootdir, - '-o', os.path.join(self.environment.get_log_dir(), 'coverage.txt')]) - elem.add_item('DESC', 'Generating text coverage report.') - elem.write(outfile) - # Alias that runs the target defined above - self.create_target_alias('meson-coverage-text', outfile) - if lcov_exe and genhtml_exe: - 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, '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) - elem = NinjaBuildElement(self.all_outputs, os.path.join(htmloutdir, 'index.html'), 'CUSTOM_COMMAND', '') - - subproject_dir = self.build.get_subproject_dir() - command = [lcov_exe, - '--directory', self.environment.get_build_dir(), - '--capture', - '--output-file', covinfo, - '--no-checksum', - '&&', lcov_exe, - '--extract', - covinfo, - os.path.join(self.environment.get_source_dir(), '*'), - '--output-file', covinfo, - '&&', lcov_exe, - '--remove', - covinfo, - os.path.join(self.environment.get_source_dir(), subproject_dir, '*'), - '--output-file', covinfo, - '&&', genhtml_exe, - '--prefix', self.environment.get_build_dir(), - '--output-directory', htmloutdir, - '--title', 'Code coverage', - '--legend', - '--show-details', - covinfo] - elem.add_item('COMMAND', command) - elem.add_item('DESC', 'Generating HTML coverage report.') - elem.write(outfile) - elif gcovr_exe and gcovr_new_rootdir: - added_rule = True - htmloutdir = os.path.join(self.environment.get_log_dir(), 'coveragereport') - 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) - elem = NinjaBuildElement(self.all_outputs, os.path.join(htmloutdir, 'index.html'), 'CUSTOM_COMMAND', '') - command = [gcovr_exe, '--html', '--html-details', '-r', self.environment.get_build_dir(), - '-o', os.path.join(htmloutdir, 'index.html')] - elem.add_item('COMMAND', command) - elem.add_item('DESC', 'Generating HTML coverage report.') - elem.write(outfile) - if not added_rule: - mlog.warning('coverage requested but neither gcovr nor lcov/genhtml found.') + e = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', 'PHONY') + self.generate_coverage_command(e, ['--xml']) + e.add_item('description', 'Generates XML coverage report.') + e.write(outfile) + # Alias that runs the target defined above + self.create_target_alias('meson-coverage-xml', outfile) + + e = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', 'PHONY') + self.generate_coverage_command(e, ['--text']) + e.add_item('description', 'Generates text coverage report.') + e.write(outfile) + # Alias that runs the target defined above + self.create_target_alias('meson-coverage-text', outfile) + + e = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'CUSTOM_COMMAND', 'PHONY') + self.generate_coverage_command(e, ['--html']) + e.add_item('description', 'Generates HTML coverage report.') + e.write(outfile) + # Alias that runs the target defined above + self.create_target_alias('meson-coverage-html', outfile) def generate_install(self, outfile): install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') @@ -1204,7 +1156,7 @@ int dummy; abs_vala_file = os.path.join(self.environment.get_build_dir(), vala_file) if PurePath(os.path.commonpath((abs_srcbasedir, abs_vala_file))) == PurePath(abs_srcbasedir): vala_c_subdir = PurePath(abs_vala_file).parent.relative_to(abs_srcbasedir) - vala_c_file = os.path.join(vala_c_subdir, vala_c_file) + vala_c_file = os.path.join(str(vala_c_subdir), vala_c_file) else: path_to_target = os.path.join(self.build_to_src, target.get_subdir()) if vala_file.startswith(path_to_target): @@ -1521,7 +1473,7 @@ int dummy; # gcc-ar blindly pass the --plugin argument to `ar` and you cannot pass # options as arguments while using the @file.rsp syntax. # See: https://github.com/mesonbuild/meson/issues/1646 - if mesonlib.is_windows() and not isinstance(static_linker, ArLinker): + if static_linker.can_linker_accept_rsp(): command_template = ''' command = {executable} @$out.rsp rspfile = $out.rsp rspfile_content = $LINK_ARGS {output_args} $in @@ -1576,7 +1528,7 @@ int dummy; except KeyError: pass rule = 'rule %s%s_LINKER\n' % (langname, crstr) - if mesonlib.is_windows(): + if compiler.can_linker_accept_rsp(): command_template = ''' command = {executable} @$out.rsp rspfile = $out.rsp rspfile_content = $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing @@ -1705,12 +1657,12 @@ rule FORTRAN_DEP_HACK if getattr(self, 'created_llvm_ir_rule', False): return rule = 'rule llvm_ir{}_COMPILER\n'.format('_CROSS' if is_cross else '') - if mesonlib.is_windows(): + if compiler.can_linker_accept_rsp(): command_template = ' command = {executable} @$out.rsp\n' \ ' rspfile = $out.rsp\n' \ - ' rspfile_content = {cross_args} $ARGS {output_args} {compile_only_args} $in\n' + ' rspfile_content = $ARGS{cross_args} {output_args} {compile_only_args} $in\n' else: - command_template = ' command = {executable} {cross_args} $ARGS {output_args} {compile_only_args} $in\n' + command_template = ' command = {executable} $ARGS {cross_args} {output_args} {compile_only_args} $in\n' command = command_template.format( executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]), cross_args=' '.join(self.get_cross_info_lang_args(compiler.language, is_cross)), @@ -1766,13 +1718,13 @@ rule FORTRAN_DEP_HACK d = quote_func(d) quoted_depargs.append(d) cross_args = self.get_cross_info_lang_args(langname, is_cross) - if mesonlib.is_windows(): + if compiler.can_linker_accept_rsp(): command_template = ''' command = {executable} @$out.rsp rspfile = $out.rsp - rspfile_content = {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in + rspfile_content = $ARGS {cross_args} {dep_args} {output_args} {compile_only_args} $in ''' else: - command_template = ' command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n' + command_template = ' command = {executable} $ARGS {cross_args} {dep_args} {output_args} {compile_only_args} $in\n' command = command_template.format( executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]), cross_args=' '.join(cross_args), @@ -1817,7 +1769,7 @@ rule FORTRAN_DEP_HACK output = '' else: output = ' '.join(compiler.get_output_args('$out')) - command = " command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n".format( + command = " command = {executable} $ARGS {cross_args} {dep_args} {output_args} {compile_only_args} $in\n".format( executable=' '.join(compiler.get_exelist()), cross_args=' '.join(cross_args), dep_args=' '.join(quoted_depargs), @@ -2169,7 +2121,10 @@ rule FORTRAN_DEP_HACK # Hence, we must reverse the list so that the order is preserved. for i in reversed(target.get_include_dirs()): basedir = i.get_curdir() - for d in i.get_incdirs(): + # We should iterate include dirs in reversed orders because + # -Ipath will add to begin of array. And without reverse + # flags will be added in reversed order. + for d in reversed(i.get_incdirs()): # Avoid superfluous '/.' at the end of paths when d is '.' if d not in ('', '.'): expdir = os.path.join(basedir, d) @@ -2196,6 +2151,11 @@ rule FORTRAN_DEP_HACK # near the end since these are supposed to override everything else. commands += self.escape_extra_args(compiler, target.get_extra_args(compiler.get_language())) + + # D specific additional flags + if compiler.language == 'd': + commands += compiler.get_feature_args(target.d_features, self.build_to_src) + # Add source dir and build dir. Project-specific and target-specific # include paths must override per-target compile args, include paths # from external dependencies, internal dependencies, and from @@ -2289,9 +2249,6 @@ rule FORTRAN_DEP_HACK depelem.write(outfile) commands += compiler.get_module_outdir_args(self.get_target_private_dir(target)) - if compiler.language == 'd': - commands += compiler.get_feature_args(target.d_features, self.build_to_src) - element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src) for d in header_deps: if isinstance(d, File): @@ -2435,7 +2392,7 @@ rule FORTRAN_DEP_HACK commands += linker.get_pic_args() # Add -Wl,-soname arguments on Linux, -install_name on OS X commands += linker.get_soname_args(target.prefix, target.name, target.suffix, - abspath, target.soversion, + abspath, target.soversion, target.ltversion, isinstance(target, build.SharedModule)) # This is only visited when building for Windows using either GCC or Visual Studio if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): @@ -2453,6 +2410,74 @@ rule FORTRAN_DEP_HACK target_args = self.build_target_link_arguments(linker, target.link_whole_targets) return linker.get_link_whole_for(target_args) if len(target_args) else [] + def guess_library_absolute_path(self, libname, search_dirs, prefixes, suffixes): + for directory in search_dirs: + for suffix in suffixes: + for prefix in prefixes: + trial = os.path.join(directory, prefix + libname + '.' + suffix) + if os.path.isfile(trial): + return trial + + def guess_external_link_dependencies(self, linker, target, commands, internal): + # Ideally the linker would generate dependency information that could be used. + # But that has 2 problems: + # * currently ld can not create dependency information in a way that ninja can use: + # https://sourceware.org/bugzilla/show_bug.cgi?id=22843 + # * Meson optimizes libraries from the same build using the symbol extractor. + # Just letting ninja use ld generated dependencies would undo this optimization. + search_dirs = [] + libs = [] + absolute_libs = [] + + build_dir = self.environment.get_build_dir() + # the following loop sometimes consumes two items from command in one pass + it = iter(commands) + for item in it: + if item in internal and not item.startswith('-'): + continue + + if item.startswith('-L'): + if len(item) > 2: + path = item[2:] + else: + try: + path = next(it) + except StopIteration: + mlog.warning("Generated linker command has -L argument without following path") + break + if not os.path.isabs(path): + path = os.path.join(build_dir, path) + search_dirs.append(path) + elif item.startswith('-l'): + if len(item) > 2: + libs.append(item[2:]) + else: + try: + libs.append(next(it)) + except StopIteration: + mlog.warning("Generated linker command has '-l' argument without following library name") + break + elif os.path.isabs(item) and self.environment.is_library(item) and os.path.isfile(item): + absolute_libs.append(item) + + guessed_dependencies = [] + # TODO The get_library_naming requirement currently excludes link targets that use d or fortran as their main linker + if hasattr(linker, 'get_library_naming'): + search_dirs += linker.get_library_dirs() + prefixes_static, suffixes_static = linker.get_library_naming(self.environment, 'static', strict=True) + prefixes_shared, suffixes_shared = linker.get_library_naming(self.environment, 'shared', strict=True) + for libname in libs: + # be conservative and record most likely shared and static resolution, because we don't know exactly + # which one the linker will prefer + static_resolution = self.guess_library_absolute_path(libname, search_dirs, prefixes_static, suffixes_static) + shared_resolution = self.guess_library_absolute_path(libname, search_dirs, prefixes_shared, suffixes_shared) + if static_resolution: + guessed_dependencies.append(os.path.realpath(static_resolution)) + if shared_resolution: + guessed_dependencies.append(os.path.realpath(shared_resolution)) + + return guessed_dependencies + absolute_libs + def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]): if isinstance(target, build.StaticLibrary): linker_base = 'STATIC' @@ -2519,12 +2544,15 @@ rule FORTRAN_DEP_HACK dependencies = [] else: dependencies = target.get_dependencies() - commands += self.build_target_link_arguments(linker, dependencies) + internal = self.build_target_link_arguments(linker, dependencies) + commands += internal # For 'automagic' deps: Boost and GTest. Also dependency('threads'). # pkg-config puts the thread flags itself via `Cflags:` for d in target.external_deps: if d.need_threads(): commands += linker.thread_link_flags(self.environment) + elif d.need_openmp(): + commands += linker.openmp_flags() # Only non-static built targets need link args and link dependencies if not isinstance(target, build.StaticLibrary): commands += target.link_args @@ -2543,6 +2571,10 @@ rule FORTRAN_DEP_HACK # symbols from those can be found here. This is needed when the # *_winlibs that we want to link to are static mingw64 libraries. commands += linker.get_option_link_args(self.environment.coredata.compiler_options) + + dep_targets = [] + dep_targets.extend(self.guess_external_link_dependencies(linker, target, commands, internal)) + # Set runtime-paths so we can run executables without needing to set # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows. if has_path_sep(target.name): @@ -2566,7 +2598,7 @@ rule FORTRAN_DEP_HACK # Convert from GCC-style link argument naming to the naming used by the # current compiler. commands = commands.to_native() - dep_targets = [self.get_dependency_filename(t) for t in dependencies] + dep_targets.extend([self.get_dependency_filename(t) for t in dependencies]) dep_targets.extend([self.get_dependency_filename(t) for t in target.link_depends]) elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 28e6722..22383dc 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -227,7 +227,7 @@ class Vs2010Backend(backends.Backend): def generate_solution(self, sln_filename, projlist): default_projlist = self.get_build_by_default_targets() - with open(sln_filename, 'w') as ofile: + with open(sln_filename, 'w', encoding='utf-8') as ofile: ofile.write('Microsoft Visual Studio Solution File, Format ' 'Version 11.00\n') ofile.write('# Visual Studio ' + self.vs_version + '\n') @@ -575,7 +575,7 @@ class Vs2010Backend(backends.Backend): tree.write(ofname, encoding='utf-8', xml_declaration=True) # ElementTree can not do prettyprinting so do it manually doc = xml.dom.minidom.parse(ofname) - with open(ofname, 'w') as of: + with open(ofname, 'w', encoding='utf-8') as of: of.write(doc.toprettyxml()) def gen_vcxproj(self, target, ofname, guid): @@ -769,7 +769,8 @@ class Vs2010Backend(backends.Backend): # These are per-target, but we still add them as per-file because we # need them to be looked in first. for d in reversed(target.get_include_dirs()): - for i in d.get_incdirs(): + # reversed is used to keep order of includes + for i in reversed(d.get_incdirs()): curdir = os.path.join(d.get_curdir(), i) args.append('-I' + self.relpath(curdir, target.subdir)) # build dir args.append('-I' + os.path.join(proj_to_src_root, curdir)) # src dir @@ -823,7 +824,10 @@ class Vs2010Backend(backends.Backend): for d in reversed(target.get_external_deps()): # Cflags required by external deps might have UNIX-specific flags, # so filter them out if needed - d_compile_args = compiler.unix_args_to_native(d.get_compile_args()) + if isinstance(d, dependencies.OpenMPDependency): + d_compile_args = compiler.openmp_flags() + else: + d_compile_args = compiler.unix_args_to_native(d.get_compile_args()) for arg in d_compile_args: if arg.startswith(('-D', '/D')): define = arg[2:] @@ -914,11 +918,17 @@ class Vs2010Backend(backends.Backend): for dep in target.get_external_deps(): # Extend without reordering or de-dup to preserve `-L -l` sets # https://github.com/mesonbuild/meson/issues/1718 - extra_link_args.extend_direct(dep.get_link_args()) + if isinstance(dep, dependencies.OpenMPDependency): + extra_link_args.extend_direct(compiler.openmp_flags()) + else: + extra_link_args.extend_direct(dep.get_link_args()) for d in target.get_dependencies(): if isinstance(d, build.StaticLibrary): for dep in d.get_external_deps(): - extra_link_args.extend_direct(dep.get_link_args()) + if isinstance(dep, dependencies.OpenMPDependency): + extra_link_args.extend_direct(compiler.openmp_flags()) + else: + extra_link_args.extend_direct(dep.get_link_args()) # Add link args for c_* or cpp_* build options. Currently this only # adds c_winlibs and cpp_winlibs when building for Windows. This needs # to be after all internal and external libraries so that unresolved @@ -945,7 +955,8 @@ class Vs2010Backend(backends.Backend): self.add_project_reference(root, tvcxproj, tid) else: # Other libraries go into AdditionalDependencies - additional_links.append(linkname) + if linkname not in additional_links: + additional_links.append(linkname) for lib in self.get_custom_target_provided_libraries(target): additional_links.append(self.relpath(lib, self.get_target_dir(target))) additional_objects = [] @@ -1117,7 +1128,7 @@ if %%errorlevel%% neq 0 goto :VCEnd''' igroup = ET.SubElement(root, 'ItemGroup') rulefile = os.path.join(self.environment.get_scratch_dir(), 'regen.rule') if not os.path.exists(rulefile): - with open(rulefile, 'w') as f: + with open(rulefile, 'w', encoding='utf-8') as f: f.write("# Meson regen file.") custombuild = ET.SubElement(igroup, 'CustomBuild', Include=rulefile) message = ET.SubElement(custombuild, 'Message') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 3ff68ed..e707053 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -26,67 +26,64 @@ from .mesonlib import get_filenames_templates_dict, substitute_values from .mesonlib import for_windows, for_darwin, for_cygwin, for_android, has_path_sep from .compilers import is_object, clike_langs, sort_clike, lang_suffixes -known_basic_kwargs = {'install': True, - 'c_pch': True, - 'cpp_pch': True, - 'c_args': True, - 'objc_args': True, - 'objcpp_args': True, - 'cpp_args': True, - 'cs_args': True, - 'vala_args': True, - 'fortran_args': True, - 'd_args': True, - 'd_import_dirs': True, - 'd_unittest': True, - 'd_module_versions': True, - 'java_args': True, - 'rust_args': True, - 'link_args': True, - 'link_depends': True, - 'link_with': True, - 'link_whole': True, - 'implicit_include_directories': True, - 'include_directories': True, - 'dependencies': True, - 'install_dir': True, - 'main_class': True, - 'name_suffix': True, - 'gui_app': True, - 'extra_files': True, - 'install_rpath': True, - 'build_rpath': True, - 'resources': True, - 'sources': True, - 'objects': True, - 'native': True, - 'build_by_default': True, - 'override_options': True, - } - -# These contain kwargs supported by both static and shared libraries. These are -# combined here because a library() call might be shared_library() or -# static_library() at runtime based on the configuration. -# FIXME: Find a way to pass that info down here so we can have proper target -# kwargs checking when specifically using shared_library() or static_library(). -known_lib_kwargs = known_basic_kwargs.copy() -known_lib_kwargs.update({'version': True, # Only for shared libs - 'soversion': True, # Only for shared libs - 'name_prefix': True, - 'vs_module_defs': True, # Only for shared libs - 'vala_header': True, - 'vala_vapi': True, - 'vala_gir': True, - 'pic': True, # Only for static libs - 'rust_crate_type': True, # Only for Rust libs - }) - -known_exe_kwargs = known_basic_kwargs.copy() -known_exe_kwargs.update({'implib': True, - 'export_dynamic': True - }) -known_jar_kwargs = known_basic_kwargs.copy() -known_jar_kwargs.update({'target_type': 'jar'}) +pch_kwargs = set(['c_pch', 'cpp_pch']) + +lang_arg_kwargs = set([ + 'c_args', + 'cpp_args', + 'd_args', + 'd_import_dirs', + 'd_unittest', + 'd_module_versions', + 'fortran_args', + 'java_args', + 'objc_args', + 'objcpp_args', + 'rust_args', + 'vala_args', + 'cs_args', +]) + +vala_kwargs = set(['vala_header', 'vala_gir', 'vala_vapi']) +rust_kwargs = set(['rust_crate_type']) +cs_kwargs = set(['resources', 'cs_args']) + +buildtarget_kwargs = set([ + 'build_by_default', + 'build_rpath', + 'dependencies', + 'extra_files', + 'gui_app', + 'link_with', + 'link_whole', + 'link_args', + 'link_depends', + 'implicit_include_directories', + 'include_directories', + 'install', + 'install_rpath', + 'install_dir', + 'name_prefix', + 'name_suffix', + 'native', + 'objects', + 'override_options', + 'sources', +]) + +known_build_target_kwargs = ( + buildtarget_kwargs | + lang_arg_kwargs | + pch_kwargs | + vala_kwargs | + rust_kwargs | + cs_kwargs) + +known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic'} +known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs'} +known_shmod_kwargs = known_build_target_kwargs +known_stlib_kwargs = known_build_target_kwargs | {'pic'} +known_jar_kwargs = known_exe_kwargs | {'main_class'} class InvalidArguments(MesonException): pass @@ -124,6 +121,8 @@ class Build: self.dep_manifest = {} self.cross_stdlibs = {} self.test_setups = {} + self.find_overrides = {} + self.searched_programs = set() # The list of all programs that have been searched for. def add_compiler(self, compiler): if self.static_linker is None and compiler.needs_static_linker(): @@ -214,9 +213,10 @@ class ExtractedObjects: ''' Holds a list of sources for which the objects must be extracted ''' - def __init__(self, target, srclist, is_unity): + def __init__(self, target, srclist, genlist, is_unity): self.target = target self.srclist = srclist + self.genlist = genlist if is_unity: self.check_unity_compatible() @@ -310,11 +310,16 @@ a hard error in the future.''' % name) def get_id(self): # This ID must also be a valid file name on all OSs. # It should also avoid shell metacharacters for obvious - # reasons. - base = self.name + self.type_suffix() - if self.subproject == '': - return base - return self.subproject + '@@' + base + # reasons. '@' is not used as often as '_' in source code names. + # In case of collisions consider using checksums. + # FIXME replace with assert when slash in names is prohibited + name_part = self.name.replace('/', '@').replace('\\', '@') + assert not has_path_sep(self.type_suffix()) + myid = name_part + self.type_suffix() + if self.subdir: + subdir_part = self.subdir.replace('/', '@').replace('\\', '@') + myid = subdir_part + '@@' + myid + return myid def process_kwargs(self, kwargs): if 'build_by_default' in kwargs: @@ -337,6 +342,8 @@ a hard error in the future.''' % name) class BuildTarget(Target): + known_kwargs = known_build_target_kwargs + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): super().__init__(name, subdir, subproject, True) self.is_cross = is_cross @@ -380,6 +387,7 @@ class BuildTarget(Target): self.process_compilers_late() self.validate_sources() self.validate_cross_install(environment) + self.check_module_linking() def __lt__(self, other): return self.get_id() < other.get_id() @@ -395,7 +403,7 @@ class BuildTarget(Target): def check_unknown_kwargs(self, kwargs): # Override this method in derived classes that have more # keywords. - self.check_unknown_kwargs_int(kwargs, known_basic_kwargs) + self.check_unknown_kwargs_int(kwargs, self.known_kwargs) def check_unknown_kwargs_int(self, kwargs, known_kwargs): unknowns = [] @@ -625,13 +633,17 @@ class BuildTarget(Target): if not isinstance(src, str): raise MesonException('Object extraction arguments must be strings.') src = File(False, self.subdir, src) + # FIXME: It could be a generated source if src not in self.sources: raise MesonException('Tried to extract unknown source %s.' % src) obj_src.append(src) - return ExtractedObjects(self, obj_src, self.is_unity) + return ExtractedObjects(self, obj_src, [], self.is_unity) def extract_all_objects(self): - return ExtractedObjects(self, self.sources, self.is_unity) + # FIXME: We should add support for transitive extract_objects() + if self.objects: + raise MesonException('Cannot extract objects from a target that itself has extracted objects') + return ExtractedObjects(self, self.sources, self.generated, self.is_unity) def get_all_link_deps(self): return self.get_transitive_link_deps() @@ -798,12 +810,16 @@ This will become a hard error in a future Meson release.''') def get_extra_args(self, language): return self.extra_args.get(language, []) - def get_dependencies(self): + def get_dependencies(self, exclude=None): transitive_deps = [] + if exclude is None: + exclude = [] for t in itertools.chain(self.link_targets, self.link_whole_targets): + if t in transitive_deps or t in exclude: + continue transitive_deps.append(t) if isinstance(t, StaticLibrary): - transitive_deps += t.get_dependencies() + transitive_deps += t.get_dependencies(transitive_deps + exclude) return transitive_deps def get_source_subdir(self): @@ -846,13 +862,14 @@ This will become a hard error in a future Meson release.''') self.link(l) for l in dep.whole_libraries: self.link_whole(l) - # Those parts that are external. - extpart = dependencies.InternalDependency('undefined', - [], - dep.compile_args, - dep.link_args, - [], [], [], []) - self.external_deps.append(extpart) + if dep.compile_args or dep.link_args: + # Those parts that are external. + extpart = dependencies.InternalDependency('undefined', + [], + dep.compile_args, + dep.link_args, + [], [], [], []) + self.external_deps.append(extpart) # Deps of deps. self.add_deps(dep.ext_deps) elif isinstance(dep, dependencies.Dependency): @@ -1027,6 +1044,15 @@ You probably should put it in link_with instead.''') def is_linkable_target(self): return False + def check_module_linking(self): + ''' + Warn if shared modules are linked with target: (link_with) #2865 + ''' + for link_target in self.link_targets: + if isinstance(link_target, SharedModule): + mlog.warning('''target links against shared modules. This is not +recommended as it can lead to undefined behaviour on some platforms''') + return class Generator: def __init__(self, args, kwargs): @@ -1174,6 +1200,8 @@ class GeneratedList: return self.extra_args class Executable(BuildTarget): + known_kwargs = known_exe_kwargs + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) # Unless overridden, executables have no suffix or prefix. Except on @@ -1186,7 +1214,11 @@ class Executable(BuildTarget): for_cygwin(is_cross, environment) or 'cs' in self.compilers): self.suffix = 'exe' else: - self.suffix = '' + if ('c' in self.compilers and self.compilers['c'].get_id().startswith('arm') or + 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('arm')): + self.suffix = 'axf' + else: + self.suffix = '' self.filename = self.name if self.suffix: self.filename += '.' + self.suffix @@ -1229,9 +1261,6 @@ class Executable(BuildTarget): def type_suffix(self): return "@exe" - def check_unknown_kwargs(self, kwargs): - self.check_unknown_kwargs_int(kwargs, known_exe_kwargs) - def get_import_filename(self): """ The name of the import library that will be outputted by the compiler @@ -1249,6 +1278,8 @@ class Executable(BuildTarget): return self.is_linkwithable class StaticLibrary(BuildTarget): + known_kwargs = known_stlib_kwargs + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options: kwargs['pic'] = environment.coredata.base_options['b_staticpic'].value @@ -1287,9 +1318,6 @@ class StaticLibrary(BuildTarget): def type_suffix(self): return "@sta" - def check_unknown_kwargs(self, kwargs): - self.check_unknown_kwargs_int(kwargs, known_lib_kwargs) - def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) if 'rust_crate_type' in kwargs: @@ -1303,6 +1331,8 @@ class StaticLibrary(BuildTarget): return True class SharedLibrary(BuildTarget): + known_kwargs = known_shlib_kwargs + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): self.soversion = None self.ltversion = None @@ -1491,9 +1521,6 @@ class SharedLibrary(BuildTarget): else: raise InvalidArguments('Invalid rust_crate_type "{0}": must be a string.'.format(rust_crate_type)) - def check_unknown_kwargs(self, kwargs): - self.check_unknown_kwargs_int(kwargs, known_lib_kwargs) - def get_import_filename(self): """ The name of the import library that will be outputted by the compiler @@ -1549,6 +1576,8 @@ class SharedLibrary(BuildTarget): # A shared library that is meant to be used with dlopen rather than linking # into something else. class SharedModule(SharedLibrary): + known_kwargs = known_shmod_kwargs + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): if 'version' in kwargs: raise MesonException('Shared modules must not specify the version kwarg.') @@ -1558,19 +1587,20 @@ class SharedModule(SharedLibrary): self.import_filename = None class CustomTarget(Target): - known_kwargs = {'input': True, - 'output': True, - 'command': True, - 'capture': False, - 'install': True, - 'install_dir': True, - 'build_always': True, - 'depends': True, - 'depend_files': True, - 'depfile': True, - 'build_by_default': True, - 'override_options': True, - } + known_kwargs = set([ + 'input', + 'output', + 'command', + 'capture', + 'install', + 'install_dir', + 'build_always', + 'depends', + 'depend_files', + 'depfile', + 'build_by_default', + 'override_options', + ]) def __init__(self, name, subdir, subproject, kwargs, absolute_paths=False): super().__init__(name, subdir, subproject, False) @@ -1804,6 +1834,8 @@ class RunTarget(Target): return "@run" class Jar(BuildTarget): + known_kwargs = known_jar_kwargs + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) for s in self.sources: @@ -1826,9 +1858,6 @@ class Jar(BuildTarget): # All jar targets are installable. pass - def check_unknown_kwargs(self, kwargs): - self.check_unknown_kwargs_int(kwargs, known_jar_kwargs) - class CustomTargetIndex: """A special opaque object returned by indexing a CustomTarget. This object diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index 84c87fb..288b3f5 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -54,10 +54,13 @@ __all__ = [ 'FortranCompiler', 'G95FortranCompiler', 'GnuCCompiler', + 'ElbrusCCompiler', 'GnuCompiler', 'GnuCPPCompiler', + 'ElbrusCPPCompiler', 'GnuDCompiler', 'GnuFortranCompiler', + 'ElbrusFortranCompiler', 'GnuObjCCompiler', 'GnuObjCPPCompiler', 'IntelCompiler', @@ -115,16 +118,20 @@ from .compilers import ( IntelCompiler, ) from .c import ( + ArmCCompiler, CCompiler, ClangCCompiler, GnuCCompiler, + ElbrusCCompiler, IntelCCompiler, VisualStudioCCompiler, ) from .cpp import ( + ArmCPPCompiler, CPPCompiler, ClangCPPCompiler, GnuCPPCompiler, + ElbrusCPPCompiler, IntelCPPCompiler, VisualStudioCPPCompiler, ) @@ -139,6 +146,7 @@ from .fortran import ( FortranCompiler, G95FortranCompiler, GnuFortranCompiler, + ElbrusFortranCompiler, IntelFortranCompiler, NAGFortranCompiler, Open64FortranCompiler, diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index e0cccc3..1230e3f 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -31,11 +31,13 @@ from .compilers import ( msvc_winlibs, vs32_instruction_set_args, vs64_instruction_set_args, + ArmCompiler, ClangCompiler, Compiler, CompilerArgs, CrossNoRunException, GnuCompiler, + ElbrusCompiler, IntelCompiler, RunResult, ) @@ -84,7 +86,7 @@ class CCompiler(Compiler): # Almost every compiler uses this for disabling warnings return ['-w'] - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): return [] def split_shlib_to_parts(self, fname): @@ -294,6 +296,8 @@ class CCompiler(Compiler): args += d.get_compile_args() if d.need_threads(): args += self.thread_flags(env) + elif d.need_openmp(): + args += self.openmp_flags() if mode == 'link': # Add link flags needed to find dependencies args += d.get_link_args() @@ -320,24 +324,20 @@ class CCompiler(Compiler): return args def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): - args = self._get_compiler_check_args(env, extra_args, dependencies, mode) - # We only want to compile; not link - with self.compile(code, args.to_native(), mode) as p: + with self._build_wrapper(code, env, extra_args, dependencies, mode) as p: return p.returncode == 0 - def _links_wrapper(self, code, env, extra_args, dependencies): - "Shares common code between self.links and self.run" - args = self._get_compiler_check_args(env, extra_args, dependencies, mode='link') - return self.compile(code, args) + def _build_wrapper(self, code, env, extra_args, dependencies=None, mode='compile', want_output=False): + args = self._get_compiler_check_args(env, extra_args, dependencies, mode) + return self.compile(code, args.to_native(), mode, want_output=want_output) def links(self, code, env, extra_args=None, dependencies=None): - with self._links_wrapper(code, env, extra_args, dependencies) as p: - return p.returncode == 0 + return self.compiles(code, env, extra_args, dependencies, mode='link') def run(self, code, env, extra_args=None, dependencies=None): if self.is_cross and self.exe_wrapper is None: raise CrossNoRunException('Can not run test applications in this cross environment.') - with self._links_wrapper(code, env, extra_args, dependencies) as p: + with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: if p.returncode != 0: mlog.debug('Could not compile test file %s: %d\n' % ( p.input_name, @@ -367,24 +367,52 @@ class CCompiler(Compiler): return self.compiles(t.format(**fargs), env, extra_args, dependencies) def cross_compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies): + # Try user's guess first if isinstance(guess, int): if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies): return guess - cur = low - while low < high: - cur = int((low + high) / 2) - if cur == low: - break - - if self._compile_int('%s >= %d' % (expression, cur), prefix, env, extra_args, dependencies): - low = cur + # If no bounds are given, compute them in the limit of int32 + maxint = 0x7fffffff + minint = -0x80000000 + if not isinstance(low, int) or not isinstance(high, int): + if self._compile_int('%s >= 0' % (expression), prefix, env, extra_args, dependencies): + low = cur = 0 + while self._compile_int('%s > %d' % (expression, cur), prefix, env, extra_args, dependencies): + low = cur + 1 + if low > maxint: + raise EnvironmentException('Cross-compile check overflowed') + cur = cur * 2 + 1 + if cur > maxint: + cur = maxint + high = cur else: + low = cur = -1 + while self._compile_int('%s < %d' % (expression, cur), prefix, env, extra_args, dependencies): + high = cur - 1 + if high < minint: + raise EnvironmentException('Cross-compile check overflowed') + cur = cur * 2 + if cur < minint: + cur = minint + low = cur + else: + # Sanity check limits given by user + if high < low: + raise EnvironmentException('high limit smaller than low limit') + condition = '%s <= %d && %s >= %d' % (expression, high, expression, low) + if not self._compile_int(condition, prefix, env, extra_args, dependencies): + raise EnvironmentException('Value out of given range') + + # Binary search + while low != high: + cur = low + int((high - low) / 2) + if self._compile_int('%s <= %d' % (expression, cur), prefix, env, extra_args, dependencies): high = cur + else: + low = cur + 1 - if self._compile_int('%s == %d' % (expression, cur), prefix, env, extra_args, dependencies): - return cur - raise EnvironmentException('Cross-compile check overflowed') + return low def compute_int(self, expression, low, high, guess, prefix, env, extra_args=None, dependencies=None): if extra_args is None: @@ -416,7 +444,7 @@ class CCompiler(Compiler): }}''' if not self.compiles(t.format(**fargs), env, extra_args, dependencies): return -1 - return self.cross_compute_int('sizeof(%s)' % typename, 1, 128, None, prefix, env, extra_args, dependencies) + return self.cross_compute_int('sizeof(%s)' % typename, None, None, None, prefix, env, extra_args, dependencies) def sizeof(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: @@ -454,7 +482,7 @@ class CCompiler(Compiler): char c; {type} target; }};''' - return self.cross_compute_int('offsetof(struct tmp, target)', 1, 1024, None, t.format(**fargs), env, extra_args, dependencies) + return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t.format(**fargs), env, extra_args, dependencies) def alignment(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: @@ -708,7 +736,7 @@ class CCompiler(Compiler): args = self.get_cross_extra_flags(env, link=False) args += self.get_compiler_check_args() n = 'symbols_have_underscore_prefix' - with self.compile(code, args, 'compile') as p: + with self.compile(code, args, 'compile', want_output=True) as p: if p.returncode != 0: m = 'BUG: Unable to compile {!r} check: {}' raise RuntimeError(m.format(n, p.stdo)) @@ -726,7 +754,7 @@ class CCompiler(Compiler): return False raise RuntimeError('BUG: {!r} check failed unexpectedly'.format(n)) - def get_library_naming(self, env, libtype): + def get_library_naming(self, env, libtype, strict=False): ''' Get library prefixes and suffixes for the target platform ordered by priority @@ -734,7 +762,10 @@ class CCompiler(Compiler): stlibext = ['a'] # We've always allowed libname to be both `foo` and `libfoo`, # and now people depend on it - prefixes = ['lib', ''] + if strict and self.id != 'msvc': # lib prefix is not usually used with msvc + prefixes = ['lib'] + else: + prefixes = ['lib', ''] # Library suffixes and prefixes if for_darwin(env.is_cross_build(), env): shlibext = ['dylib'] @@ -806,16 +837,35 @@ class CCompiler(Compiler): return [] return ['-pthread'] + def linker_to_compiler_args(self, args): + return args + + def has_arguments(self, args, env, code, mode): + return self.compiles(code, env, extra_args=args, mode=mode) + def has_multi_arguments(self, args, env): - for arg in args: + for arg in args[:]: + # some compilers, e.g. GCC, don't warn for unsupported warning-disable + # flags, so when we are testing a flag like "-Wno-forgotten-towel", also + # check the equivalent enable flag too "-Wforgotten-towel" + if arg.startswith('-Wno-'): + args.append('-W' + arg[5:]) if arg.startswith('-Wl,'): - mlog.warning('''{} looks like a linker argument, but has_argument -and other similar methods only support checking compiler arguments. -Using them to check linker arguments are never supported, and results -are likely to be wrong regardless of the compiler you are using. -'''.format(arg)) - return self.compiles('int i;\n', env, extra_args=args) + mlog.warning('{} looks like a linker argument, ' + 'but has_argument and other similar methods only ' + 'support checking compiler arguments. Using them ' + 'to check linker arguments are never supported, ' + 'and results are likely to be wrong regardless of ' + 'the compiler you are using. has_link_argument or ' + 'other similar method can be used instead.' + .format(arg)) + code = 'int i;\n' + return self.has_arguments(args, env, code, mode='compile') + def has_multi_link_arguments(self, args, env): + args = self.linker_to_compiler_args(args) + code = 'int main(int argc, char **argv) { return 0; }' + return self.has_arguments(args, env, code, mode='link') class ClangCCompiler(ClangCompiler, CCompiler): def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None, **kwargs): @@ -888,6 +938,29 @@ class GnuCCompiler(GnuCompiler, CCompiler): return ['-fpch-preprocess', '-include', os.path.basename(header)] +class ElbrusCCompiler(GnuCCompiler, ElbrusCompiler): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs): + GnuCCompiler.__init__(self, exelist, version, gcc_type, is_cross, exe_wrapper, defines, **kwargs) + ElbrusCompiler.__init__(self, gcc_type, defines) + + # It does support some various ISO standards and c/gnu 90, 9x, 1x in addition to those which GNU CC supports. + def get_options(self): + opts = {'c_std': coredata.UserComboOption('c_std', 'C language standard to use', + ['none', 'c89', 'c90', 'c9x', 'c99', 'c1x', 'c11', + 'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11', + 'iso9899:2011', 'iso9899:1990', 'iso9899:199409', 'iso9899:1999'], + 'none')} + return opts + + # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error. + # So we should explicitly fail at this case. + def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): + if funcname == 'lchmod': + return False + else: + return super().has_function(funcname, prefix, env, extra_args, dependencies) + + class IntelCCompiler(IntelCompiler, CCompiler): def __init__(self, exelist, version, icc_type, is_cross, exe_wrapper=None, **kwargs): CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs) @@ -918,8 +991,8 @@ class IntelCCompiler(IntelCompiler, CCompiler): def get_std_shared_lib_link_args(self): return ['-shared'] - def has_multi_arguments(self, args, env): - return super().has_multi_arguments(args + ['-diag-error', '10006'], env) + def has_arguments(self, args, env, code, mode): + return super().has_arguments(args + ['-diag-error', '10006'], env, code, mode) class VisualStudioCCompiler(CCompiler): @@ -1003,6 +1076,9 @@ class VisualStudioCCompiler(CCompiler): def get_linker_search_args(self, dirname): return ['/LIBPATH:' + dirname] + def linker_to_compiler_args(self, args): + return ['/link'] + args + def get_gui_app_args(self): return ['/SUBSYSTEM:WINDOWS'] @@ -1030,6 +1106,9 @@ class VisualStudioCCompiler(CCompiler): def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): return [] + def openmp_flags(self): + return ['/openmp'] + # FIXME, no idea what these should be. def thread_flags(self, env): return [] @@ -1083,24 +1162,12 @@ class VisualStudioCCompiler(CCompiler): # Visual Studio is special. It ignores some arguments it does not # understand and you can't tell it to error out on those. # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t - def has_multi_arguments(self, args, env): - warning_text = '9002' - code = 'int i;\n' - (fd, srcname) = tempfile.mkstemp(suffix='.' + self.default_suffix) - os.close(fd) - with open(srcname, 'w') as ofile: - ofile.write(code) - # Read c_args/cpp_args/etc from the cross-info file (if needed) - extra_args = self.get_cross_extra_flags(env, link=False) - extra_args += self.get_compile_only_args() - commands = self.exelist + args + extra_args + [srcname] - mlog.debug('Running VS compile:') - mlog.debug('Command line: ', ' '.join(commands)) - mlog.debug('Code:\n', code) - p, stdo, stde = Popen_safe(commands, cwd=os.path.dirname(srcname)) - if p.returncode != 0: - return False - return not(warning_text in stde or warning_text in stdo) + def has_arguments(self, args, env, code, mode): + warning_text = '4044' if mode == 'link' else '9002' + with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: + if p.returncode != 0: + return False + return not(warning_text in p.stde or warning_text in p.stdo) def get_compile_debugfile_args(self, rel_obj, pch=False): pdbarr = rel_obj.split('.')[:-1] @@ -1166,3 +1233,22 @@ class VisualStudioCCompiler(CCompiler): if 'INCLUDE' not in os.environ: return [] return os.environ['INCLUDE'].split(os.pathsep) + + +class ArmCCompiler(ArmCompiler, CCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwargs): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwargs) + ArmCompiler.__init__(self) + + def get_options(self): + opts = {'c_std': coredata.UserComboOption('c_std', 'C language standard to use', + ['none', 'c90', 'c99'], + 'none')} + return opts + + def get_option_compile_args(self, options): + args = [] + std = options['c_std'] + if std.value != 'none': + args.append('--' + std.value) + return args diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index a28a225..a2c6668 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -55,7 +55,6 @@ for _l in clike_langs: clike_suffixes += lang_suffixes[_l] clike_suffixes += ('h', 'll', 's') -# XXX: Use this in is_library()? soregex = re.compile(r'.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$') # All these are only for C-like languages; see `clike_langs` above. @@ -102,6 +101,10 @@ def is_object(fname): def is_library(fname): if hasattr(fname, 'fname'): fname = fname.fname + + if soregex.match(fname): + return True + suffix = fname.split('.')[-1] return suffix in lib_suffixes @@ -113,6 +116,13 @@ gnulike_buildtype_args = {'plain': [], 'release': ['-O3'], 'minsize': ['-Os', '-g']} +arm_buildtype_args = {'plain': [], + 'debug': ['-O0', '--debug'], + 'debugoptimized': ['-O1', '--debug'], + 'release': ['-O3', '-Otime'], + 'minsize': ['-O3', '-Ospace'], + } + msvc_buildtype_args = {'plain': [], 'debug': ["/MDd", "/ZI", "/Ob0", "/Od", "/RTC1"], 'debugoptimized': ["/MD", "/Zi", "/O2", "/Ob1"], @@ -134,6 +144,13 @@ gnulike_buildtype_linker_args = {'plain': [], 'minsize': [], } +arm_buildtype_linker_args = {'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], + } + msvc_buildtype_linker_args = {'plain': [], 'debug': [], 'debugoptimized': [], @@ -526,15 +543,22 @@ class CompilerArgs(list): def append_direct(self, arg): ''' Append the specified argument without any reordering or de-dup + except for absolute paths where the order of include search directories + is not relevant ''' - super().append(arg) + if os.path.isabs(arg): + self.append(arg) + else: + super().append(arg) def extend_direct(self, iterable): ''' Extend using the elements in the specified iterable without any - reordering or de-dup + reordering or de-dup except for absolute paths where the order of + include search directories is not relevant ''' - super().extend(iterable) + for elem in iterable: + self.append_direct(elem) def __add__(self, args): new = CompilerArgs(self, self.compiler) @@ -601,6 +625,8 @@ class Compiler: # Libraries to ignore in find_library() since they are provided by the # compiler or the C library. Currently only used for MSVC. ignore_libs = () + # Cache for the result of compiler checks which can be cached + compiler_check_cache = {} def __init__(self, exelist, version, **kwargs): if isinstance(exelist, str): @@ -659,6 +685,12 @@ class Compiler: def get_always_args(self): return [] + def can_linker_accept_rsp(self): + """ + Determines whether the linker can accept arguments using the @rsp syntax. + """ + return mesonlib.is_windows() + def get_linker_always_args(self): return [] @@ -713,20 +745,15 @@ class Compiler: def get_library_dirs(self): return [] - def has_argument(self, arg, env): - return self.has_multi_arguments([arg], env) - def has_multi_arguments(self, args, env): raise EnvironmentException( 'Language {} does not support has_multi_arguments.'.format( self.get_display_language())) - def get_supported_arguments(self, args, env): - supported_args = [] - for arg in args: - if self.has_argument(arg, env): - supported_args.append(arg) - return supported_args + def has_multi_link_arguments(self, args, env): + raise EnvironmentException( + 'Language {} does not support has_multi_link_arguments.'.format( + self.get_display_language())) def get_cross_extra_flags(self, environment, link): extra_flags = [] @@ -753,9 +780,23 @@ class Compiler: return os.path.join(dirname, 'output.' + suffix) @contextlib.contextmanager - def compile(self, code, extra_args=None, mode='link'): + def compile(self, code, extra_args=None, mode='link', want_output=False): if extra_args is None: + textra_args = None extra_args = [] + else: + textra_args = tuple(extra_args) + key = (code, textra_args, mode) + if not want_output: + if key in self.compiler_check_cache: + p = self.compiler_check_cache[key] + mlog.debug('Using cached compile:') + mlog.debug('Cached command line: ', ' '.join(p.commands), '\n') + mlog.debug('Code:\n', code) + mlog.debug('Cached compiler stdout:\n', p.stdo) + mlog.debug('Cached compiler stderr:\n', p.stde) + yield p + raise StopIteration try: with tempfile.TemporaryDirectory() as tmpdirname: if isinstance(code, str): @@ -765,12 +806,10 @@ class Compiler: ofile.write(code) elif isinstance(code, mesonlib.File): srcname = code.fname - output = self._get_compile_output(tmpdirname, mode) # Construct the compiler command-line commands = CompilerArgs(self) commands.append(srcname) - commands += extra_args commands += self.get_always_args() if mode == 'compile': commands += self.get_compile_only_args() @@ -778,7 +817,12 @@ class Compiler: if mode == 'preprocess': commands += self.get_preprocess_only_args() else: + output = self._get_compile_output(tmpdirname, mode) commands += self.get_output_args(output) + # extra_args must be last because it could contain '/link' to + # pass args to VisualStudio's linker. In that case everything + # in the command line after '/link' is given to the linker. + commands += extra_args # Generate full command-line with the exelist commands = self.get_exelist() + commands.to_native() mlog.debug('Running compile:') @@ -788,8 +832,12 @@ class Compiler: p, p.stdo, p.stde = Popen_safe(commands, cwd=tmpdirname) mlog.debug('Compiler stdout:\n', p.stdo) mlog.debug('Compiler stderr:\n', p.stde) + p.commands = commands p.input_name = srcname - p.output_name = output + if want_output: + p.output_name = output + else: + self.compiler_check_cache[key] = p yield p except (PermissionError, OSError): # On Windows antivirus programs and the like hold on to files so @@ -887,6 +935,9 @@ class Compiler: def thread_flags(self, env): return [] + def openmp_flags(self): + raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language()) + GCC_STANDARD = 0 GCC_OSX = 1 @@ -909,14 +960,16 @@ ICC_WIN = 2 GNU_LD_AS_NEEDED = '-Wl,--as-needed' APPLE_LD_AS_NEEDED = '-Wl,-dead_strip_dylibs' -def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module): +def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): if soversion is None: sostr = '' else: sostr = '.' + soversion - if gcc_type in (GCC_STANDARD, GCC_MINGW, GCC_CYGWIN): - # Might not be correct for mingw but seems to work. + if gcc_type == GCC_STANDARD: return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)] + elif gcc_type in (GCC_MINGW, GCC_CYGWIN): + # For PE/COFF the soname argument has no effect with GNU LD + return [] elif gcc_type == GCC_OSX: if is_shared_module: return [] @@ -924,7 +977,15 @@ def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, i if soversion is not None: install_name += '.' + soversion install_name += '.dylib' - return ['-install_name', os.path.join('@rpath', install_name)] + args = ['-install_name', os.path.join('@rpath', install_name)] + if version and len(version.split('.')) == 3: + splitted = version.split('.') + major = int(splitted[0]) + minor = int(splitted[1]) + revision = int(splitted[2]) + args += ['-compatibility_version', '%d' % (major + minor + 1)] + args += ['-current_version', '%d.%d' % (major + minor + 1, revision)] + return args else: raise RuntimeError('Not implemented yet.') @@ -980,7 +1041,7 @@ def gnulike_default_include_dirs(compiler, lang): stdout=subprocess.PIPE, env=env ) - stderr = p.stderr.read().decode('utf-8') + stderr = p.stderr.read().decode('utf-8', errors='replace') parse_state = 0 paths = [] for line in stderr.split('\n'): @@ -1062,8 +1123,8 @@ class GnuCompiler: def split_shlib_to_parts(self, fname): return os.path.dirname(fname), fname - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): - return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): + return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, version, is_shared_module) def get_std_shared_lib_link_args(self): return ['-shared'] @@ -1092,6 +1153,32 @@ class GnuCompiler: def get_default_include_dirs(self): return gnulike_default_include_dirs(self.exelist, self.language) + def openmp_flags(self): + return ['-fopenmp'] + + +class ElbrusCompiler(GnuCompiler): + # Elbrus compiler is nearly like GCC, but does not support + # PCH, LTO, sanitizers and color output as of version 1.21.x. + def __init__(self, gcc_type, defines): + GnuCompiler.__init__(self, gcc_type, defines) + self.id = 'lcc' + self.base_options = ['b_pgo', 'b_coverage', + 'b_ndebug', 'b_staticpic', + 'b_lundef', 'b_asneeded'] + + def get_library_dirs(self): + env = os.environ.copy() + env['LC_ALL'] = 'C' + stdo = Popen_safe(self.exelist + ['--print-search-dirs'], env=env)[1] + for line in stdo.split('\n'): + if line.startswith('libraries:'): + # lcc does not include '=' in --print-search-dirs output. + libstr = line.split(' ', 1)[1] + return libstr.split(':') + return [] + + class ClangCompiler: def __init__(self, clang_type): @@ -1138,7 +1225,7 @@ class ClangCompiler: # so it might change semantics at any time. return ['-include-pch', os.path.join(pch_dir, self.get_pch_name(header))] - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): if self.clang_type == CLANG_STANDARD: gcc_type = GCC_STANDARD elif self.clang_type == CLANG_OSX: @@ -1147,7 +1234,7 @@ class ClangCompiler: gcc_type = GCC_MINGW else: raise MesonException('Unreachable code when converting clang type to gcc type.') - return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) + return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, version, is_shared_module) def has_multi_arguments(self, args, env): myargs = ['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument'] @@ -1187,6 +1274,15 @@ class ClangCompiler: def get_default_include_dirs(self): return gnulike_default_include_dirs(self.exelist, self.language) + def openmp_flags(self): + if version_compare(self.version, '>=3.8.0'): + return ['-fopenmp'] + elif version_compare(self.version, '>=3.7.0'): + return ['-fopenmp=libomp'] + else: + # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency. + return [] + # Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1 class IntelCompiler: @@ -1221,7 +1317,7 @@ class IntelCompiler: def split_shlib_to_parts(self, fname): return os.path.dirname(fname), fname - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): if self.icc_type == ICC_STANDARD: gcc_type = GCC_STANDARD elif self.icc_type == ICC_OSX: @@ -1230,7 +1326,7 @@ class IntelCompiler: gcc_type = GCC_MINGW else: raise MesonException('Unreachable code when converting icc type to gcc type.') - return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) + return get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, version, is_shared_module) # TODO: centralise this policy more globally, instead # of fragmenting it into GnuCompiler and ClangCompiler @@ -1248,3 +1344,79 @@ class IntelCompiler: def get_default_include_dirs(self): return gnulike_default_include_dirs(self.exelist, self.language) + + def openmp_flags(self): + if version_compare(self.version, '>=15.0.0'): + return ['-qopenmp'] + else: + return ['-openmp'] + + +class ArmCompiler: + # Functionality that is common to all ARM family compilers. + def __init__(self): + if not self.is_cross: + raise EnvironmentException('armcc supports only cross-compilation.') + self.id = 'arm' + default_warn_args = [] + self.warn_args = {'1': default_warn_args, + '2': default_warn_args + [], + '3': default_warn_args + []} + # Assembly + self.can_compile_suffixes.add('s') + + def can_linker_accept_rsp(self): + return False + + def get_pic_args(self): + # FIXME: Add /ropi, /rwpi, /fpic etc. qualifiers to --apcs + return [] + + def get_buildtype_args(self, buildtype): + return arm_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return arm_buildtype_linker_args[buildtype] + + # Override CCompiler.get_always_args + def get_always_args(self): + return [] + + # Override CCompiler.get_dependency_gen_args + def get_dependency_gen_args(self, outtarget, outfile): + return [] + + # Override CCompiler.get_std_shared_lib_link_args + def get_std_shared_lib_link_args(self): + return [] + + def get_pch_use_args(self, pch_dir, header): + # FIXME: Add required arguments + # NOTE from armcc user guide: + # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05 + # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported + # PCH files." + return [] + + def get_pch_suffix(self): + # NOTE from armcc user guide: + # "Support for Precompiled Header (PCH) files is deprecated from ARM Compiler 5.05 + # onwards on all platforms. Note that ARM Compiler on Windows 8 never supported + # PCH files." + return 'pch' + + def thread_flags(self, env): + return [] + + def thread_link_flags(self, env): + return [] + + def get_linker_exelist(self): + args = ['armlink'] + return args + + def get_coverage_args(self): + return [] + + def get_coverage_link_args(self): + return [] diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 1fa6f15..4c48052 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -25,7 +25,9 @@ from .compilers import ( msvc_winlibs, ClangCompiler, GnuCompiler, + ElbrusCompiler, IntelCompiler, + ArmCompiler, ) class CPPCompiler(CCompiler): @@ -133,6 +135,29 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): return ['-fpch-preprocess', '-include', os.path.basename(header)] +class ElbrusCPPCompiler(GnuCPPCompiler, ElbrusCompiler): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs): + GnuCPPCompiler.__init__(self, exelist, version, gcc_type, is_cross, exe_wrapper, defines, **kwargs) + ElbrusCompiler.__init__(self, gcc_type, defines) + + # It does not support c++/gnu++ 17 and 1z, but still does support 0x, 1y, and gnu++98. + def get_options(self): + opts = super().get_options() + opts['cpp_std'] = coredata.UserComboOption('cpp_std', 'C++ language standard to use', + ['none', 'c++98', 'c++03', 'c++0x', 'c++11', 'c++14', 'c++1y', + 'gnu++98', 'gnu++03', 'gnu++0x', 'gnu++11', 'gnu++14', 'gnu++1y'], + 'none') + return opts + + # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error. + # So we should explicitly fail at this case. + def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): + if funcname == 'lchmod': + return False + else: + return super().has_function(funcname, prefix, env, extra_args, dependencies) + + class IntelCPPCompiler(IntelCompiler, CPPCompiler): def __init__(self, exelist, version, icc_type, is_cross, exe_wrap, **kwargs): CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs) @@ -174,20 +199,10 @@ class IntelCPPCompiler(IntelCompiler, CPPCompiler): def get_option_link_args(self, options): return [] - def has_multi_arguments(self, args, env): - for arg in args: - if arg.startswith('-Wl,'): - mlog.warning('''{} looks like a linker argument, but has_argument -and other similar methods only support checking compiler arguments. -Using them to check linker arguments are never supported, and results -are likely to be wrong regardless of the compiler you are using. -'''.format(arg)) - return super().has_multi_arguments(args + ['-diag-error', '10006'], env) - class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap, is_64): - self.language = 'cpp' + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap, is_64) self.base_options = ['b_pch'] # FIXME add lto, pgo and the like @@ -214,4 +229,31 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def get_compiler_check_args(self): # Visual Studio C++ compiler doesn't support -fpermissive, # so just use the plain C args. - return super(VisualStudioCCompiler, self).get_compiler_check_args() + return VisualStudioCCompiler.get_compiler_check_args(self) + + +class ArmCPPCompiler(ArmCompiler, CPPCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap=None, **kwargs): + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap, **kwargs) + ArmCompiler.__init__(self) + + def get_options(self): + opts = {'cpp_std': coredata.UserComboOption('cpp_std', 'C++ language standard to use', + ['none', 'c++03', 'c++11'], + 'none')} + return opts + + def get_option_compile_args(self, options): + args = [] + std = options['cpp_std'] + if std.value == 'c++11': + args.append('--cpp11') + elif std.value == 'c++03': + args.append('--cpp') + return args + + def get_option_link_args(self, options): + return [] + + def get_compiler_check_args(self): + return [] diff --git a/mesonbuild/compilers/cs.py b/mesonbuild/compilers/cs.py index f78e364..581b458 100644 --- a/mesonbuild/compilers/cs.py +++ b/mesonbuild/compilers/cs.py @@ -41,7 +41,7 @@ class CsCompiler(Compiler): def get_link_args(self, fname): return ['-r:' + fname] - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): return [] def get_werror_args(self): diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index 474e1bd..b76bfba 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -89,9 +89,9 @@ class DCompiler(Compiler): def get_std_shared_lib_link_args(self): return ['-shared'] - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): # FIXME: Make this work for Windows, MacOS and cross-compiling - return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, path, soversion, is_shared_module) + return get_gcc_soname_args(GCC_STANDARD, prefix, shlib_name, suffix, path, soversion, version, is_shared_module) def get_feature_args(self, kwargs, build_to_src): res = [] diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index f9fcc1c..9d3240f 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -27,6 +27,7 @@ from .compilers import ( gnulike_buildtype_args, gnulike_buildtype_linker_args, Compiler, + ElbrusCompiler, IntelCompiler, ) @@ -93,8 +94,8 @@ end program prog def split_shlib_to_parts(self, fname): return os.path.dirname(fname), fname - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): - return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): + return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, version, is_shared_module) def get_dependency_gen_args(self, outtarget, outfile): # Disabled until this is fixed: @@ -179,6 +180,15 @@ class GnuFortranCompiler(FortranCompiler): """ return ['-Wl,--out-implib=' + implibname] + def openmp_flags(self): + return ['-fopenmp'] + + +class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None, **kwargs): + GnuFortranCompiler.__init__(self, exelist, version, gcc_type, is_cross, exe_wrapper, defines, **kwargs) + ElbrusCompiler.__init__(self, gcc_type, defines) + class G95FortranCompiler(FortranCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags): @@ -224,6 +234,9 @@ class SunFortranCompiler(FortranCompiler): def get_module_outdir_args(self, path): return ['-moddir=' + path] + def openmp_flags(self): + return ['-xopenmp'] + class IntelFortranCompiler(IntelCompiler, FortranCompiler): std_warn_args = ['-warn', 'all'] @@ -256,6 +269,10 @@ class PathScaleFortranCompiler(FortranCompiler): def get_std_warn_args(self, level): return PathScaleFortranCompiler.std_warn_args + def openmp_flags(self): + return ['-mp'] + + class PGIFortranCompiler(FortranCompiler): std_warn_args = ['-Minform=inform'] @@ -275,6 +292,9 @@ class PGIFortranCompiler(FortranCompiler): def get_no_warn_args(self): return ['-silent'] + def openmp_flags(self): + return ['-fopenmp'] + class Open64FortranCompiler(FortranCompiler): std_warn_args = ['-fullwarn'] @@ -289,6 +309,9 @@ class Open64FortranCompiler(FortranCompiler): def get_warn_args(self, level): return Open64FortranCompiler.std_warn_args + def openmp_flags(self): + return ['-mp'] + class NAGFortranCompiler(FortranCompiler): std_warn_args = [] @@ -302,3 +325,6 @@ class NAGFortranCompiler(FortranCompiler): def get_warn_args(self, level): return NAGFortranCompiler.std_warn_args + + def openmp_flags(self): + return ['-openmp'] diff --git a/mesonbuild/compilers/java.py b/mesonbuild/compilers/java.py index a8138d7..1213d18 100644 --- a/mesonbuild/compilers/java.py +++ b/mesonbuild/compilers/java.py @@ -25,7 +25,7 @@ class JavaCompiler(Compiler): self.id = 'unknown' self.javarunner = 'java' - def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): + def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, version, is_shared_module): return [] def get_werror_args(self): diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index 9ab5c8a..6194d1a 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -35,10 +35,10 @@ class ValaCompiler(Compiler): return False # Because compiles into C. def get_output_args(self, target): - return ['-o', target] + return [] # Because compiles into C. def get_compile_only_args(self): - return ['-C'] + return [] # Because compiles into C. def get_pic_args(self): return [] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 993effc..ba4f495 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -20,6 +20,7 @@ from collections import OrderedDict from .mesonlib import MesonException from .mesonlib import default_libdir, default_libexecdir, default_prefix import ast +import argparse version = '0.46.0.dev1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] @@ -44,21 +45,17 @@ class UserOption: def validate_value(self, value): raise RuntimeError('Derived option class did not override validate_value.') + def set_value(self, newvalue): + self.value = self.validate_value(newvalue) + class UserStringOption(UserOption): def __init__(self, name, description, value, choices=None, yielding=None): super().__init__(name, description, choices, yielding) self.set_value(value) - def validate(self, value): + def validate_value(self, value): if not isinstance(value, str): raise MesonException('Value "%s" for string option "%s" is not a string.' % (str(value), self.name)) - - def set_value(self, newvalue): - self.validate(newvalue) - self.value = newvalue - - def validate_value(self, value): - self.validate(value) return value class UserBooleanOption(UserOption): @@ -66,23 +63,17 @@ class UserBooleanOption(UserOption): super().__init__(name, description, [True, False], yielding) self.set_value(value) - def tobool(self, thing): - if isinstance(thing, bool): - return thing - if thing.lower() == 'true': - return True - if thing.lower() == 'false': - return False - raise MesonException('Value %s is not boolean (true or false).' % thing) - - def set_value(self, newvalue): - self.value = self.tobool(newvalue) - def __bool__(self): return self.value def validate_value(self, value): - return self.tobool(value) + if isinstance(value, bool): + return value + if value.lower() == 'true': + return True + if value.lower() == 'false': + return False + raise MesonException('Value %s is not boolean (true or false).' % value) class UserIntegerOption(UserOption): def __init__(self, name, description, min_value, max_value, value, yielding=None): @@ -97,16 +88,16 @@ class UserIntegerOption(UserOption): c.append('<=' + str(max_value)) self.choices = ', '.join(c) - def set_value(self, newvalue): - if isinstance(newvalue, str): - newvalue = self.toint(newvalue) - if not isinstance(newvalue, int): + def validate_value(self, value): + if isinstance(value, str): + value = self.toint(value) + if not isinstance(value, int): raise MesonException('New value for integer option is not an integer.') - if self.min_value is not None and newvalue < self.min_value: - raise MesonException('New value %d is less than minimum value %d.' % (newvalue, self.min_value)) - if self.max_value is not None and newvalue > self.max_value: - raise MesonException('New value %d is more than maximum value %d.' % (newvalue, self.max_value)) - self.value = newvalue + if self.min_value is not None and value < self.min_value: + raise MesonException('New value %d is less than minimum value %d.' % (value, self.min_value)) + if self.max_value is not None and value > self.max_value: + raise MesonException('New value %d is more than maximum value %d.' % (value, self.max_value)) + return value def toint(self, valuestring): try: @@ -114,9 +105,6 @@ class UserIntegerOption(UserOption): except ValueError: raise MesonException('Value string "%s" is not convertable to an integer.' % valuestring) - def validate_value(self, value): - return self.toint(value) - class UserComboOption(UserOption): def __init__(self, name, description, choices, value, yielding=None): super().__init__(name, description, choices, yielding) @@ -127,23 +115,18 @@ class UserComboOption(UserOption): raise MesonException('Combo choice elements must be strings.') self.set_value(value) - def set_value(self, newvalue): - if newvalue not in self.choices: - optionsstring = ', '.join(['"%s"' % (item,) for item in self.choices]) - raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (newvalue, self.name, optionsstring)) - self.value = newvalue - def validate_value(self, value): if value not in self.choices: - raise MesonException('Value %s not one of accepted values.' % value) + optionsstring = ', '.join(['"%s"' % (item,) for item in self.choices]) + raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (value, self.name, optionsstring)) return value class UserArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): super().__init__(name, description, kwargs.get('choices', []), yielding=kwargs.get('yielding', None)) - self.set_value(value, user_input=False) + self.value = self.validate_value(value, user_input=False) - def validate(self, value, user_input): + def validate_value(self, value, user_input=True): # 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 @@ -176,13 +159,6 @@ This will become a hard error in the future.''') ', '.join(bad), ', '.join(self.choices))) return newvalue - def set_value(self, newvalue, user_input=True): - self.value = self.validate(newvalue, user_input) - - def validate_value(self, value): - self.validate(value) - return value - # This class contains all data that must persist over multiple # invocations of Meson. It is roughly the same thing as # cmakecache. @@ -210,7 +186,6 @@ class CoreData: self.compilers = OrderedDict() self.cross_compilers = OrderedDict() self.deps = OrderedDict() - self.modules = {} # Only to print a warning if it changes between Meson invocations. self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '') @@ -385,6 +360,20 @@ def get_builtin_option_description(optname): else: raise RuntimeError('Tried to get the description for an unknown builtin option \'%s\'.' % optname) +def get_builtin_option_action(optname): + default = builtin_options[optname][2] + if default is True: + return 'store_false' + elif default is False: + return 'store_true' + return None + +def get_builtin_option_destination(optname): + optname = optname.replace('-', '_') + if optname == 'warnlevel': + return 'warning_level' + return optname + def get_builtin_option_default(optname, prefix='', noneIfSuppress=False): if is_builtin_option(optname): o = builtin_options[optname] @@ -402,6 +391,29 @@ def get_builtin_option_default(optname, prefix='', noneIfSuppress=False): else: raise RuntimeError('Tried to get the default value for an unknown builtin option \'%s\'.' % optname) +def add_builtin_argument(p, name): + kwargs = {} + k = get_builtin_option_destination(name) + c = get_builtin_option_choices(k) + b = get_builtin_option_action(k) + h = get_builtin_option_description(k) + if not b: + h = h.rstrip('.') + ' (default: %s).' % get_builtin_option_default(k) + else: + kwargs['action'] = b + if c and not b: + kwargs['choices'] = c + default = get_builtin_option_default(k, noneIfSuppress=True) + if default is not None: + kwargs['default'] = default + else: + kwargs['default'] = argparse.SUPPRESS + p.add_argument('--' + name.replace('_', '-'), help=h, **kwargs) + +def register_builtin_arguments(parser): + for n in builtin_options: + add_builtin_argument(parser, n) + builtin_options = { 'buildtype': [UserComboOption, 'Build type to use.', ['plain', 'debug', 'debugoptimized', 'release', 'minsize'], 'debug'], 'strip': [UserBooleanOption, 'Strip targets on install.', False], @@ -422,7 +434,7 @@ builtin_options = { 'werror': [UserBooleanOption, 'Treat warnings as errors.', False], 'warning_level': [UserComboOption, 'Compiler warning level to use.', ['1', '2', '3'], '1'], 'layout': [UserComboOption, 'Build directory layout.', ['mirror', 'flat'], 'mirror'], - 'default_library': [UserComboOption, 'Default library type.', ['shared', 'static'], 'shared'], + 'default_library': [UserComboOption, 'Default library type.', ['shared', 'static', 'both'], 'shared'], 'backend': [UserComboOption, 'Backend to use.', backendlist, 'ninja'], 'stdsplit': [UserBooleanOption, 'Split stdout and stderr in test logs.', True], 'errorlogs': [UserBooleanOption, "Whether to print the logs from failing tests.", True], diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index 4796980..1c67311 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -18,7 +18,7 @@ from .base import ( # noqa: F401 ExternalDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency, PkgConfigDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language) from .dev import GMockDependency, GTestDependency, LLVMDependency, ValgrindDependency -from .misc import (MPIDependency, Python3Dependency, ThreadDependency, PcapDependency, CupsDependency, LibWmfDependency) +from .misc import (MPIDependency, OpenMPDependency, Python3Dependency, ThreadDependency, PcapDependency, CupsDependency, LibWmfDependency) from .platform import AppleFrameworks from .ui import GLDependency, GnuStepDependency, Qt4Dependency, Qt5Dependency, SDL2Dependency, WxDependency, VulkanDependency @@ -33,6 +33,7 @@ packages.update({ # From misc: 'boost': BoostDependency, 'mpi': MPIDependency, + 'openmp': OpenMPDependency, 'python3': Python3Dependency, 'threads': ThreadDependency, 'pcap': PcapDependency, @@ -53,4 +54,5 @@ packages.update({ }) _packages_accept_language.update({ 'mpi', + 'openmp', }) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 0375102..27a5fcb 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1,4 +1,4 @@ -# Copyright 2013-2017 The Meson development team +# Copyright 2013-2018 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ # This file contains the detection logic for external dependencies. # Custom logic for several other packages are in separate files. +import copy import os import re import stat @@ -134,6 +135,9 @@ class Dependency: def get_exe_args(self, compiler): return [] + def need_openmp(self): + return False + def need_threads(self): return False @@ -143,6 +147,23 @@ class Dependency: def get_configtool_variable(self, variable_name): raise DependencyException('{!r} is not a config-tool dependency'.format(self.name)) + def get_partial_dependency(self, *, compile_args=False, link_args=False, + links=False, includes=False, sources=False): + """Create a new dependency that contains part of the parent dependency. + + The following options can be inherited: + links -- all link_with arguemnts + includes -- all include_directory and -I/-isystem calls + sources -- any source, header, or generated sources + compile_args -- any compile args + link_args -- any link args + + Additionally the new dependency will have the version parameter of it's + parent (if any) and the requested values of any dependencies will be + added as well. + """ + RuntimeError('Unreachable code in partial_dependency called') + class InternalDependency(Dependency): def __init__(self, version, incdirs, compile_args, link_args, libraries, whole_libraries, sources, ext_deps): @@ -165,6 +186,21 @@ class InternalDependency(Dependency): raise DependencyException('Method "get_configtool_variable()" is ' 'invalid for an internal dependency') + def get_partial_dependency(self, *, compile_args=False, link_args=False, + links=False, includes=False, sources=False): + compile_args = self.compile_args.copy() if compile_args else [] + link_args = self.link_args.copy() if link_args else [] + libraries = self.libraries.copy() if links else [] + whole_libraries = self.whole_libraries.copy() if links else [] + sources = self.sources.copy() if sources else [] + includes = self.include_directories.copy() if includes else [] + deps = [d.get_partial_dependency( + compile_args=compile_args, link_args=link_args, links=links, + includes=includes, sources=sources) for d in self.ext_deps] + return InternalDependency( + self.version, includes, compile_args, link_args, libraries, + whole_libraries, sources, deps) + class ExternalDependency(Dependency): def __init__(self, type_name, environment, language, kwargs): @@ -208,6 +244,18 @@ class ExternalDependency(Dependency): def get_compiler(self): return self.compiler + def get_partial_dependency(self, *, compile_args=False, link_args=False, + links=False, includes=False, sources=False): + new = copy.copy(self) + if not compile_args: + new.compile_args = [] + if not link_args: + new.link_args = [] + if not sources: + new.sources = [] + + return new + class ConfigToolDependency(ExternalDependency): @@ -362,6 +410,8 @@ class PkgConfigDependency(ExternalDependency): # The class's copy of the pkg-config path. Avoids having to search for it # multiple times in the same Meson invocation. class_pkgbin = None + # We cache all pkg-config subprocess invocations to avoid redundant calls + pkgbin_cache = {} def __init__(self, name, environment, kwargs, language=None): super().__init__('pkgconfig', environment, language, kwargs) @@ -459,12 +509,22 @@ class PkgConfigDependency(ExternalDependency): return s.format(self.__class__.__name__, self.name, self.is_found, self.version_reqs) - def _call_pkgbin(self, args, env=None): - if not env: - env = os.environ + def _call_pkgbin_real(self, args, env): p, out = Popen_safe(self.pkgbin.get_command() + args, env=env)[0:2] return p.returncode, out.strip() + def _call_pkgbin(self, args, env=None): + if env is None: + fenv = env + env = os.environ + else: + fenv = frozenset(env.items()) + targs = tuple(args) + cache = PkgConfigDependency.pkgbin_cache + if (self.pkgbin, targs, fenv) not in cache: + cache[(self.pkgbin, targs, fenv)] = self._call_pkgbin_real(args, env) + return cache[(self.pkgbin, targs, fenv)] + def _convert_mingw_paths(self, args): ''' Both MSVC and native Python on Windows cannot handle MinGW-esque /c/foo @@ -872,6 +932,15 @@ class ExternalLibrary(ExternalDependency): return [] return self.link_args + def get_partial_dependency(self, *, compile_args=False, link_args=False, + links=False, includes=False, sources=False): + # External library only has link_args, so ignore the rest of the + # interface. + new = copy.copy(self) + if not link_args: + new.link_args = [] + return new + class ExtraFrameworkDependency(ExternalDependency): def __init__(self, name, required, path, env, lang, kwargs): diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 2a218be..d4525b1 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -237,6 +237,38 @@ class MPIDependency(ExternalDependency): [os.path.join(libdir, 'msmpi.lib')]) +class OpenMPDependency(ExternalDependency): + # Map date of specification release (which is the macro value) to a version. + VERSIONS = { + '201511': '4.5', + '201307': '4.0', + '201107': '3.1', + '200805': '3.0', + '200505': '2.5', + '200203': '2.0', + '199810': '1.0', + } + + def __init__(self, environment, kwargs): + language = kwargs.get('language') + super().__init__('openmp', environment, language, kwargs) + self.is_found = False + openmp_date = self.compiler.get_define('_OPENMP', '', self.env, [], [self]) + if openmp_date: + self.version = self.VERSIONS[openmp_date] + if self.compiler.has_header('omp.h', '', self.env, dependencies=[self]): + self.is_found = True + else: + mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.') + if self.is_found: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES'), self.version) + else: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO')) + + def need_openmp(self): + return True + + class ThreadDependency(ExternalDependency): def __init__(self, environment, kwargs): super().__init__('threads', environment, None, {}) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index ff7c706..6920b8d 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -38,6 +38,8 @@ from .compilers import ( is_source, ) from .compilers import ( + ArmCCompiler, + ArmCPPCompiler, ClangCCompiler, ClangCPPCompiler, ClangObjCCompiler, @@ -48,6 +50,9 @@ from .compilers import ( GnuFortranCompiler, GnuObjCCompiler, GnuObjCPPCompiler, + ElbrusCCompiler, + ElbrusCPPCompiler, + ElbrusFortranCompiler, IntelCCompiler, IntelCPPCompiler, IntelFortranCompiler, @@ -223,6 +228,9 @@ def detect_cpu(compilers): except mesonlib.MesonException: pass return 'x86_64' + if trial == 'e2k': + # Make more precise CPU detection for Elbrus platform. + trial = platform.processor().lower() # Add fixes here as bugs are reported. return trial @@ -421,6 +429,15 @@ class Environment: return dot.join((major, minor, patch)) @staticmethod + def get_lcc_version_from_defines(defines): + dot = '.' + generation_and_major = defines.get('__LCC__', '100') + generation = generation_and_major[:1] + major = generation_and_major[1:] + minor = defines.get('__LCC_MINOR__', '0') + return dot.join((generation, major, minor)) + + @staticmethod def get_gnu_compiler_type(defines): # Detect GCC type (Apple, MinGW, Cygwin, Unix) if '__APPLE__' in defines: @@ -504,6 +521,8 @@ class Environment: if found_cl in watcom_cls: continue arg = '/?' + elif 'armcc' in compiler[0]: + arg = '--vsn' else: arg = '--version' try: @@ -513,15 +532,27 @@ class Environment: continue version = search_version(out) full_version = out.split('\n', 1)[0] + + guess_gcc_or_lcc = False if 'Free Software Foundation' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + + if guess_gcc_or_lcc: defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) - version = self.get_gnu_version_from_defines(defines) - cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler + if guess_gcc_or_lcc == 'lcc': + version = self.get_lcc_version_from_defines(defines) + cls = ElbrusCCompiler if lang == 'c' else ElbrusCPPCompiler + else: + version = self.get_gnu_version_from_defines(defines) + cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler return cls(ccache + compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version) + if 'clang' in out: if 'Apple' in out or mesonlib.for_darwin(want_cross, self): cltype = CLANG_OSX @@ -550,6 +581,9 @@ class Environment: inteltype = ICC_STANDARD cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler return cls(ccache + compiler, version, inteltype, is_cross, exe_wrap, full_version=full_version) + if 'ARM' in out: + cls = ArmCCompiler if lang == 'c' else ArmCPPCompiler + return cls(ccache + compiler, version, is_cross, exe_wrap, full_version=full_version) self._handle_exceptions(popen_exceptions, compilers) def detect_c_compiler(self, want_cross): @@ -574,14 +608,25 @@ class Environment: version = search_version(out) full_version = out.split('\n', 1)[0] + guess_gcc_or_lcc = False if 'GNU Fortran' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + + if guess_gcc_or_lcc: defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) - version = self.get_gnu_version_from_defines(defines) - return GnuFortranCompiler(compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version) + if guess_gcc_or_lcc == 'lcc': + version = self.get_lcc_version_from_defines(defines) + cls = ElbrusFortranCompiler + else: + version = self.get_gnu_version_from_defines(defines) + cls = GnuFortranCompiler + return cls(compiler, version, gtype, is_cross, exe_wrap, defines, full_version=full_version) if 'G95' in out: return G95FortranCompiler(compiler, version, is_cross, exe_wrap, full_version=full_version) @@ -626,7 +671,7 @@ class Environment: popen_exceptions[' '.join(compiler + arg)] = e continue version = search_version(out) - if 'Free Software Foundation' in out: + if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out): defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' @@ -653,7 +698,7 @@ class Environment: popen_exceptions[' '.join(compiler + arg)] = e continue version = search_version(out) - if 'Free Software Foundation' in out: + if 'Free Software Foundation' in out or ('e2k' in out and 'lcc' in out): defines = self.get_gnu_compiler_defines(compiler) if not defines: popen_exceptions[' '.join(compiler)] = 'no pre-processor defines' @@ -674,7 +719,7 @@ class Environment: except OSError: raise EnvironmentException('Could not execute Java compiler "%s"' % ' '.join(exelist)) version = search_version(err) - if 'javac' in err: + if 'javac' in out or 'javac' in err: return JavaCompiler(exelist, version) raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 1b680df..a4c93de 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -26,7 +26,7 @@ from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, DependencyException from .interpreterbase import InterpreterBase from .interpreterbase import check_stringlist, noPosargs, noKwargs, stringArgs, permittedKwargs, permittedMethodKwargs -from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode +from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler from .modules import ModuleReturnValue @@ -275,6 +275,7 @@ class DependencyHolder(InterpreterObject, ObjectHolder): 'version': self.version_method, 'get_pkgconfig_variable': self.pkgconfig_method, 'get_configtool_variable': self.configtool_method, + 'partial_dependency': self.partial_dependency_method, }) def type_name_method(self, args, kwargs): @@ -306,12 +307,18 @@ class DependencyHolder(InterpreterObject, ObjectHolder): raise InterpreterException('Variable name must be a string.') return self.held_object.get_configtool_variable(varname) + def partial_dependency_method(self, args, kwargs): + if args: + raise InterpreterException('partial_dependency takes no positional arguments') + return DependencyHolder(self.held_object.get_partial_dependency(**kwargs)) + class InternalDependencyHolder(InterpreterObject, ObjectHolder): def __init__(self, dep): InterpreterObject.__init__(self) ObjectHolder.__init__(self, dep) self.methods.update({'found': self.found_method, 'version': self.version_method, + 'partial_dependency': self.partial_dependency_method, }) def found_method(self, args, kwargs): @@ -320,6 +327,11 @@ class InternalDependencyHolder(InterpreterObject, ObjectHolder): def version_method(self, args, kwargs): return self.held_object.get_version() + def partial_dependency_method(self, args, kwargs): + if args: + raise InterpreterException('get_partial_dependency takes no positional arguments') + return DependencyHolder(self.held_object.get_partial_dependency(**kwargs)) + class ExternalProgramHolder(InterpreterObject, ObjectHolder): def __init__(self, ep): InterpreterObject.__init__(self) @@ -346,7 +358,9 @@ class ExternalLibraryHolder(InterpreterObject, ObjectHolder): def __init__(self, el): InterpreterObject.__init__(self) ObjectHolder.__init__(self, el) - self.methods.update({'found': self.found_method}) + self.methods.update({'found': self.found_method, + 'partial_dependency': self.partial_dependency_method, + }) def found(self): return self.held_object.found() @@ -366,6 +380,11 @@ class ExternalLibraryHolder(InterpreterObject, ObjectHolder): def get_exe_args(self): return self.held_object.get_exe_args() + def partial_dependency_method(self, args, kwargs): + if args: + raise InterpreterException('partial_dependency takes no positional arguments') + return DependencyHolder(self.held_object.get_partial_dependency(**kwargs)) + class GeneratorHolder(InterpreterObject, ObjectHolder): def __init__(self, interpreter, args, kwargs): InterpreterObject.__init__(self) @@ -605,6 +624,31 @@ class StaticLibraryHolder(BuildTargetHolder): class SharedLibraryHolder(BuildTargetHolder): def __init__(self, target, interp): super().__init__(target, interp) + # Set to True only when called from self.func_shared_lib(). + target.shared_library_only = False + +class BothLibrariesHolder(BuildTargetHolder): + def __init__(self, shared_holder, static_holder, interp): + # FIXME: This build target always represents the shared library, but + # that should be configurable. + super().__init__(shared_holder.held_object, interp) + self.shared_holder = shared_holder + self.static_holder = static_holder + self.methods.update({'get_shared_lib': self.get_shared_lib_method, + 'get_static_lib': self.get_static_lib_method, + }) + + def __repr__(self): + r = '<{} {}: {}, {}: {}>' + h1 = self.shared_holder.held_object + h2 = self.static_holder.held_object + return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) + + def get_shared_lib_method(self, args, kwargs): + return self.shared_holder + + def get_static_lib_method(self, args, kwargs): + return self.static_holder class SharedModuleHolder(BuildTargetHolder): def __init__(self, target, interp): @@ -718,6 +762,10 @@ class CompilerHolder(InterpreterObject): 'has_multi_arguments': self.has_multi_arguments_method, 'get_supported_arguments': self.get_supported_arguments_method, 'first_supported_argument': self.first_supported_argument_method, + 'has_link_argument': self.has_link_argument_method, + 'has_multi_link_arguments': self.has_multi_link_arguments_method, + 'get_supported_link_arguments': self.get_supported_link_arguments_method, + 'first_supported_link_argument': self.first_supported_link_argument_method, 'unittest_args': self.unittest_args_method, 'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method, }) @@ -961,20 +1009,20 @@ class CompilerHolder(InterpreterObject): check_stringlist(args) expression = args[0] prefix = kwargs.get('prefix', '') - l = kwargs.get('low', -1024) - h = kwargs.get('high', 1024) + low = kwargs.get('low', None) + high = kwargs.get('high', None) guess = kwargs.get('guess', None) if not isinstance(prefix, str): raise InterpreterException('Prefix argument of compute_int must be a string.') - if not isinstance(l, int): + if low is not None and not isinstance(low, int): raise InterpreterException('Low argument of compute_int must be an int.') - if not isinstance(h, int): + if high is not None and not isinstance(high, int): raise InterpreterException('High argument of compute_int must be an int.') if guess is not None and not isinstance(guess, int): raise InterpreterException('Guess argument of compute_int must be an int.') extra_args = self.determine_args(kwargs) deps = self.determine_dependencies(kwargs) - res = self.compiler.compute_int(expression, l, h, guess, prefix, self.environment, extra_args, deps) + res = self.compiler.compute_int(expression, low, high, guess, prefix, self.environment, extra_args, deps) mlog.log('Computing int of "%s": %d' % (expression, res)) return res @@ -1160,14 +1208,8 @@ class CompilerHolder(InterpreterObject): def has_argument_method(self, args, kwargs): args = mesonlib.stringlistify(args) if len(args) != 1: - raise InterpreterException('Has_arg takes exactly one argument.') - result = self.compiler.has_argument(args[0], self.environment) - if result: - h = mlog.green('YES') - else: - h = mlog.red('NO') - mlog.log('Compiler for {} supports argument {}:'.format(self.compiler.get_display_language(), args[0]), h) - return result + raise InterpreterException('has_argument takes exactly one argument.') + return self.has_multi_arguments_method(args, kwargs) @permittedMethodKwargs({}) def has_multi_arguments_method(self, args, kwargs): @@ -1186,26 +1228,58 @@ class CompilerHolder(InterpreterObject): @permittedMethodKwargs({}) def get_supported_arguments_method(self, args, kwargs): args = mesonlib.stringlistify(args) - result = self.compiler.get_supported_arguments(args, self.environment) - if len(result) == len(args): + supported_args = [] + for arg in args: + if self.has_argument_method(arg, kwargs): + supported_args.append(arg) + return supported_args + + @permittedMethodKwargs({}) + def first_supported_argument_method(self, args, kwargs): + for i in mesonlib.stringlistify(args): + if self.has_argument_method(i, kwargs): + mlog.log('First supported argument:', mlog.bold(i)) + return [i] + mlog.log('First supported argument:', mlog.red('None')) + return [] + + @permittedMethodKwargs({}) + def has_link_argument_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + if len(args) != 1: + raise InterpreterException('has_link_argument takes exactly one argument.') + return self.has_multi_link_arguments_method(args, kwargs) + + @permittedMethodKwargs({}) + def has_multi_link_arguments_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + result = self.compiler.has_multi_link_arguments(args, self.environment) + if result: h = mlog.green('YES') - elif len(result) > 0: - h = mlog.yellow('SOME') else: h = mlog.red('NO') mlog.log( - 'Compiler for {} supports arguments {}:'.format( + 'Compiler for {} supports link arguments {}:'.format( self.compiler.get_display_language(), ' '.join(args)), h) return result @permittedMethodKwargs({}) - def first_supported_argument_method(self, args, kwargs): + def get_supported_link_arguments_method(self, args, kwargs): + args = mesonlib.stringlistify(args) + supported_args = [] + for arg in args: + if self.has_link_argument_method(arg, kwargs): + supported_args.append(arg) + return supported_args + + @permittedMethodKwargs({}) + def first_supported_link_argument_method(self, args, kwargs): for i in mesonlib.stringlistify(args): - if self.compiler.has_argument(i, self.environment): - mlog.log('First supported argument:', mlog.bold(i)) + if self.has_link_argument_method(i, kwargs): + mlog.log('First supported link argument:', mlog.bold(i)) return [i] - mlog.log('First supported argument:', mlog.red('None')) + mlog.log('First supported link argument:', mlog.red('None')) return [] ModuleState = namedtuple('ModuleState', [ @@ -1281,6 +1355,7 @@ class MesonMain(InterpreterObject): 'add_install_script': self.add_install_script_method, 'add_postconf_script': self.add_postconf_script_method, 'install_dependency_manifest': self.install_dependency_manifest_method, + 'override_find_program': self.override_find_program_method, 'project_version': self.project_version_method, 'project_license': self.project_license_method, 'version': self.version_method, @@ -1393,6 +1468,26 @@ class MesonMain(InterpreterObject): raise InterpreterException('Argument must be a string.') self.build.dep_manifest_name = args[0] + def override_find_program_method(self, args, kwargs): + if len(args) != 2: + raise InterpreterException('Override needs two arguments') + name = args[0] + exe = args[1] + if not isinstance(name, str): + raise InterpreterException('First argument must be a string') + if hasattr(exe, 'held_object'): + exe = exe.held_object + if isinstance(exe, mesonlib.File): + abspath = exe.absolute_path(self.interpreter.environment.source_dir, + self.interpreter.environment.build_dir) + if not os.path.exists(abspath): + raise InterpreterException('Tried to override %s with a file that does not exist.' % name) + exe = dependencies.ExternalProgram(abspath) + if not isinstance(exe, dependencies.ExternalProgram): + # FIXME, make this work if the exe is an Executable target. + raise InterpreterException('Second argument must be an external program.') + self.interpreter.add_find_program_override(name, exe) + def project_version_method(self, args, kwargs): return self.build.dep_manifest[self.interpreter.active_projectname]['version'] @@ -1420,71 +1515,17 @@ class MesonMain(InterpreterObject): raise InterpreterException('Unknown cross property: %s.' % propname) -pch_kwargs = set(['c_pch', 'cpp_pch']) - -lang_arg_kwargs = set([ - 'c_args', - 'cpp_args', - 'd_args', - 'd_import_dirs', - 'd_unittest', - 'd_module_versions', - 'fortran_args', - 'java_args', - 'objc_args', - 'objcpp_args', - 'rust_args', - 'vala_args', - 'cs_args', -]) - -vala_kwargs = set(['vala_header', 'vala_gir', 'vala_vapi']) -rust_kwargs = set(['rust_crate_type']) -cs_kwargs = set(['resources', 'cs_args']) - -buildtarget_kwargs = set([ - 'build_by_default', - 'build_rpath', - 'dependencies', - 'extra_files', - 'gui_app', - 'link_with', - 'link_whole', - 'link_args', - 'link_depends', - 'implicit_include_directories', - 'include_directories', - 'install', - 'install_rpath', - 'install_dir', - 'name_prefix', - 'name_suffix', - 'native', - 'objects', - 'override_options', - 'pic', - 'sources', - 'vs_module_defs', -]) - -build_target_common_kwargs = ( - buildtarget_kwargs | - lang_arg_kwargs | - pch_kwargs | - vala_kwargs | - rust_kwargs | - cs_kwargs) - -exe_kwargs = (build_target_common_kwargs) | {'implib', 'export_dynamic'} -shlib_kwargs = (build_target_common_kwargs) | {'version', 'soversion'} -shmod_kwargs = shlib_kwargs -stlib_kwargs = shlib_kwargs - -jar_kwargs = exe_kwargs.copy() -jar_kwargs.update(['main_class']) - -build_target_kwargs = exe_kwargs.copy() -build_target_kwargs.update(['target_type']) +known_library_kwargs = ( + build.known_shlib_kwargs | + build.known_stlib_kwargs +) + +known_build_target_kwargs = ( + known_library_kwargs | + build.known_exe_kwargs | + build.known_jar_kwargs | + {'target_type'} +) permitted_kwargs = {'add_global_arguments': {'language'}, 'add_global_link_arguments': {'language'}, @@ -1493,12 +1534,12 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'add_project_arguments': {'language'}, 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env'}, 'benchmark': {'args', 'env', 'should_fail', 'timeout', 'workdir', 'suite'}, - 'build_target': build_target_kwargs, - 'configure_file': {'input', 'output', 'configuration', 'command', 'install_dir', 'capture', 'install'}, + 'build_target': known_build_target_kwargs, + 'configure_file': {'input', 'output', 'configuration', 'command', 'install_dir', 'capture', 'install', 'format'}, 'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, 'dependency': {'default_options', 'fallback', 'language', 'main', 'method', 'modules', 'optional_modules', 'native', 'required', 'static', 'version'}, 'declare_dependency': {'include_directories', 'link_with', 'sources', 'dependencies', 'compile_args', 'link_args', 'link_whole', 'version'}, - 'executable': exe_kwargs, + 'executable': build.known_exe_kwargs, 'find_program': {'required', 'native'}, 'generator': {'arguments', 'output', 'depfile', 'capture', 'preserve_path_from'}, 'include_directories': {'is_system'}, @@ -1506,12 +1547,14 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'install_headers': {'install_dir', 'subdir'}, 'install_man': {'install_dir'}, 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, - 'jar': jar_kwargs, + 'jar': build.known_jar_kwargs, 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, 'run_target': {'command', 'depends'}, - 'shared_library': shlib_kwargs, - 'shared_module': shmod_kwargs, - 'static_library': stlib_kwargs, + 'shared_library': build.known_shlib_kwargs, + 'shared_module': build.known_shmod_kwargs, + 'static_library': build.known_stlib_kwargs, + 'both_libraries': known_library_kwargs, + 'library': known_library_kwargs, 'subdir': {'if_found'}, 'subproject': {'version', 'default_options'}, 'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite'}, @@ -1522,7 +1565,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, class Interpreter(InterpreterBase): def __init__(self, build, backend, subproject='', subdir='', subproject_dir='subprojects', - default_project_options=[]): + modules = None, default_project_options=[]): super().__init__(build.environment.get_source_dir(), subdir) self.an_unpicklable_object = mesonlib.an_unpicklable_object self.build = build @@ -1530,6 +1573,10 @@ class Interpreter(InterpreterBase): self.coredata = self.environment.get_coredata() self.backend = backend self.subproject = subproject + if modules is None: + self.modules = {} + else: + self.modules = modules # Subproject directory is usually the name of the subproject, but can # be different for dependencies provided by wrap files. self.subproject_directory_name = subdir.split(os.path.sep)[-1] @@ -1608,12 +1655,14 @@ class Interpreter(InterpreterBase): 'run_command': self.func_run_command, 'set_variable': self.func_set_variable, 'subdir': self.func_subdir, + 'subdir_done': self.func_subdir_done, 'subproject': self.func_subproject, 'shared_library': self.func_shared_lib, 'shared_module': self.func_shared_module, 'static_library': self.func_static_lib, + 'both_libraries': self.func_both_lib, 'test': self.func_test, - 'vcs_tag': self.func_vcs_tag, + 'vcs_tag': self.func_vcs_tag }) if 'MESON_UNIT_TEST' in os.environ: self.funcs.update({'exception': self.func_exception}) @@ -1637,6 +1686,8 @@ class Interpreter(InterpreterBase): return DataHolder(item) elif isinstance(item, dependencies.InternalDependency): return InternalDependencyHolder(item) + elif isinstance(item, dependencies.ExternalDependency): + return DependencyHolder(item) elif isinstance(item, dependencies.ExternalProgram): return ExternalProgramHolder(item) elif hasattr(item, 'held_object'): @@ -1708,13 +1759,13 @@ class Interpreter(InterpreterBase): plainname = modname.split('-', 1)[1] mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node) modname = 'unstable_' + plainname - if modname not in self.environment.coredata.modules: + if modname not in self.modules: try: module = importlib.import_module('mesonbuild.modules.' + modname) except ImportError: raise InvalidArguments('Module "%s" does not exist' % (modname, )) - self.environment.coredata.modules[modname] = module.initialize() - return ModuleHolder(modname, self.environment.coredata.modules[modname], self) + self.modules[modname] = module.initialize(self) + return ModuleHolder(modname, self.modules[modname], self) @stringArgs @noKwargs @@ -1889,21 +1940,24 @@ external dependencies (including libraries) must go to "dependencies".''') subdir = os.path.join(self.subproject_dir, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True - mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') - subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir, - mesonlib.stringlistify(kwargs.get('default_options', []))) - subi.subprojects = self.subprojects - - subi.subproject_stack = self.subproject_stack + [dirname] - current_active = self.active_projectname - subi.run() + mlog.log() + with mlog.nested(): + mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') + subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir, + self.modules, mesonlib.stringlistify(kwargs.get('default_options', []))) + subi.subprojects = self.subprojects + + subi.subproject_stack = self.subproject_stack + [dirname] + current_active = self.active_projectname + subi.run() + mlog.log('\nSubproject', mlog.bold(dirname), 'finished.') + if 'version' in kwargs: pv = subi.project_version wanted = kwargs['version'] if pv == 'undefined' or not mesonlib.version_compare(pv, wanted): raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted)) self.active_projectname = current_active - mlog.log('\nSubproject', mlog.bold(dirname), 'finished.') self.build.subprojects[dirname] = subi.project_version self.subprojects.update(subi.subprojects) self.subprojects[dirname] = SubprojectHolder(subi) @@ -2224,10 +2278,12 @@ to directly access options of other subprojects.''') self.coredata.external_args.setdefault(lang, []).append(optvalue) # Otherwise, look for definitions from environment # variables such as CFLAGS. - if not comp.get_language() in self.coredata.external_args: - (preproc_args, compile_args, link_args) = environment.get_args_from_envvars(comp) + (preproc_args, compile_args, link_args) = environment.get_args_from_envvars(comp) + if not comp.get_language() in self.coredata.external_preprocess_args: self.coredata.external_preprocess_args[comp.get_language()] = preproc_args + if not comp.get_language() in self.coredata.external_args: self.coredata.external_args[comp.get_language()] = compile_args + if not comp.get_language() in self.coredata.external_link_args: self.coredata.external_link_args[comp.get_language()] = link_args self.build.add_compiler(comp) if need_cross_compiler: @@ -2250,7 +2306,7 @@ to directly access options of other subprojects.''') break self.coredata.base_options[optname] = oobj - def program_from_cross_file(self, prognames): + def program_from_cross_file(self, prognames, silent=False): bins = self.environment.cross_info.config['binaries'] for p in prognames: if hasattr(p, 'held_object'): @@ -2261,11 +2317,11 @@ to directly access options of other subprojects.''') raise InterpreterException('Executable name must be a string.') if p in bins: exename = bins[p] - extprog = dependencies.ExternalProgram(exename) + extprog = dependencies.ExternalProgram(exename, silent=silent) progobj = ExternalProgramHolder(extprog) return progobj - def program_from_system(self, args): + def program_from_system(self, args, silent=False): # Search for scripts relative to current subdir. # Do not cache found programs because find_program('foobar') # might give different results when run from different source dirs. @@ -2284,11 +2340,55 @@ to directly access options of other subprojects.''') else: raise InvalidArguments('find_program only accepts strings and ' 'files, not {!r}'.format(exename)) - extprog = dependencies.ExternalProgram(exename, search_dir=search_dir) + extprog = dependencies.ExternalProgram(exename, search_dir=search_dir, + silent=silent) progobj = ExternalProgramHolder(extprog) if progobj.found(): return progobj + def program_from_overrides(self, command_names, silent=False): + for name in command_names: + if not isinstance(name, str): + continue + if name in self.build.find_overrides: + exe = self.build.find_overrides[name] + if not silent: + mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), + '(overridden: %s)' % ' '.join(exe.command)) + return ExternalProgramHolder(exe) + return None + + def store_name_lookups(self, command_names): + for name in command_names: + if isinstance(name, str): + self.build.searched_programs.add(name) + + def add_find_program_override(self, name, exe): + if name in self.build.searched_programs: + raise InterpreterException('Tried to override finding of executable "%s" which has already been found.' + % name) + if name in self.build.find_overrides: + raise InterpreterException('Tried to override executable "%s" which has already been overridden.' + % name) + self.build.find_overrides[name] = exe + + def find_program_impl(self, args, native=False, required=True, silent=True): + if not isinstance(args, list): + args = [args] + progobj = self.program_from_overrides(args, silent=silent) + if progobj is None and self.build.environment.is_cross_build(): + if not native: + progobj = self.program_from_cross_file(args, silent=silent) + if progobj is None: + progobj = self.program_from_system(args, silent=silent) + if required and (progobj is None or not progobj.found()): + raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args)) + if progobj is None: + return ExternalProgramHolder(dependencies.NonExistingExternalProgram()) + # Only store successful lookups + self.store_name_lookups(args) + return progobj + @permittedKwargs(permitted_kwargs['find_program']) def func_find_program(self, node, args, kwargs): if not args: @@ -2296,23 +2396,16 @@ to directly access options of other subprojects.''') required = kwargs.get('required', True) if not isinstance(required, bool): raise InvalidArguments('"required" argument must be a boolean.') - progobj = None - if self.build.environment.is_cross_build(): - use_native = kwargs.get('native', False) - if not isinstance(use_native, bool): - raise InvalidArguments('Argument to "native" must be a boolean.') - if not use_native: - progobj = self.program_from_cross_file(args) - if progobj is None: - progobj = self.program_from_system(args) - if required and (progobj is None or not progobj.found()): - raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args)) - if progobj is None: - return ExternalProgramHolder(dependencies.NonExistingExternalProgram()) - return progobj + use_native = kwargs.get('native', False) + if not isinstance(use_native, bool): + raise InvalidArguments('Argument to "native" must be a boolean.') + return self.find_program_impl(args, native=use_native, required=required, silent=False) def func_find_library(self, node, args, kwargs): - raise InvalidCode('find_library() is removed, use the corresponding method in a compiler object instead.') + raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n' + 'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n' + 'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n' + ) def _find_cached_dep(self, name, kwargs): # Check if we want this as a cross-dep or a native-dep @@ -2351,7 +2444,7 @@ to directly access options of other subprojects.''') def check_subproject_version(wanted, found): if wanted == 'undefined': return True - if found == 'undefined' or not mesonlib.version_compare(found, wanted): + if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]: return False return True @@ -2428,10 +2521,13 @@ to directly access options of other subprojects.''') dep = None # Search for it outside the project - try: - dep = dependencies.find_external_dependency(name, self.environment, kwargs) - except DependencyException as e: - exception = e + if self.coredata.wrap_mode != WrapMode.forcefallback or 'fallback' not in kwargs: + try: + dep = dependencies.find_external_dependency(name, self.environment, kwargs) + except DependencyException as e: + exception = e + else: + exception = DependencyException("fallback for %s not found" % name) # Search inside the projects list if not dep or not dep.found(): @@ -2537,20 +2633,24 @@ root and issuing %s. @permittedKwargs(permitted_kwargs['shared_library']) def func_shared_lib(self, node, args, kwargs): - return self.build_target(node, args, kwargs, SharedLibraryHolder) + holder = self.build_target(node, args, kwargs, SharedLibraryHolder) + holder.held_object.shared_library_only = True + return holder + + @permittedKwargs(permitted_kwargs['both_libraries']) + def func_both_lib(self, node, args, kwargs): + return self.build_both_libraries(node, args, kwargs) @permittedKwargs(permitted_kwargs['shared_module']) def func_shared_module(self, node, args, kwargs): return self.build_target(node, args, kwargs, SharedModuleHolder) + @permittedKwargs(permitted_kwargs['library']) def func_library(self, node, args, kwargs): - if self.coredata.get_builtin_option('default_library') == 'shared': - return self.func_shared_lib(node, args, kwargs) - return self.func_static_lib(node, args, kwargs) + return self.build_library(node, args, kwargs) @permittedKwargs(permitted_kwargs['jar']) def func_jar(self, node, args, kwargs): - kwargs['target_type'] = 'jar' return self.build_target(node, args, kwargs, JarHolder) @permittedKwargs(permitted_kwargs['build_target']) @@ -2559,15 +2659,17 @@ root and issuing %s. raise InterpreterException('Missing target_type keyword argument') target_type = kwargs.pop('target_type') if target_type == 'executable': - return self.func_executable(node, args, kwargs) + return self.build_target(node, args, kwargs, ExecutableHolder) elif target_type == 'shared_library': - return self.func_shared_lib(node, args, kwargs) + return self.build_target(node, args, kwargs, SharedLibraryHolder) elif target_type == 'static_library': - return self.func_static_lib(node, args, kwargs) + return self.build_target(node, args, kwargs, StaticLibraryHolder) + elif target_type == 'both_libraries': + return self.build_both_libraries(node, args, kwargs) elif target_type == 'library': - return self.func_library(node, args, kwargs) + return self.build_library(node, args, kwargs) elif target_type == 'jar': - return self.func_jar(node, args, kwargs) + return self.build_target(node, args, kwargs, JarHolder) else: raise InterpreterException('Unknown target_type.') @@ -2609,6 +2711,14 @@ root and issuing %s. return self.func_custom_target(node, [kwargs['output']], kwargs) @stringArgs + def func_subdir_done(self, node, args, kwargs): + if len(kwargs) > 0: + raise InterpreterException('exit does not take named arguments') + if len(args) > 0: + raise InterpreterException('exit does not take any arguments') + raise SubdirDoneRequest() + + @stringArgs @permittedKwargs(permitted_kwargs['custom_target']) def func_custom_target(self, node, args, kwargs): if len(args) != 1: @@ -2692,7 +2802,7 @@ root and issuing %s. exe = args[1] if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)): if isinstance(exe, mesonlib.File): - exe = self.func_find_program(node, (args[1], ), {}) + exe = self.func_find_program(node, args[1], {}) else: raise InterpreterException('Second argument must be executable.') par = kwargs.get('is_parallel', True) @@ -2893,6 +3003,16 @@ root and issuing %s. if 'command' not in kwargs: raise InterpreterException('"capture" keyword requires "command" keyword.') + if 'format' in kwargs: + format = kwargs['format'] + if not isinstance(format, str): + raise InterpreterException('"format" keyword must be a string.') + else: + format = 'meson' + + if format not in ('meson', 'cmake', 'cmake@'): + raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".') + # Validate input inputfile = None ifile_abs = None @@ -2932,7 +3052,7 @@ root and issuing %s. if inputfile is not None: os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) missing_variables = mesonlib.do_conf_file(ifile_abs, ofile_abs, - conf.held_object) + conf.held_object, format) if missing_variables: var_list = ", ".join(map(repr, sorted(missing_variables))) mlog.warning( @@ -3211,6 +3331,41 @@ different subdirectory. if idname not in self.coredata.target_guids: self.coredata.target_guids[idname] = str(uuid.uuid4()).upper() + def build_both_libraries(self, node, args, kwargs): + shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder) + + # Check if user forces non-PIC static library. + pic = True + if 'pic' in kwargs: + pic = kwargs['pic'] + elif 'b_staticpic' in self.environment.coredata.base_options: + pic = self.environment.coredata.base_options['b_staticpic'].value + + if pic: + # Exclude sources from args and kwargs to avoid building them twice + static_args = [args[0]] + static_kwargs = kwargs.copy() + static_kwargs['sources'] = [] + static_kwargs['objects'] = shared_holder.held_object.extract_all_objects() + else: + static_args = args + static_kwargs = kwargs + + static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder) + + return BothLibrariesHolder(shared_holder, static_holder, self) + + def build_library(self, node, args, kwargs): + default_library = self.coredata.get_builtin_option('default_library') + if default_library == 'shared': + return self.build_target(node, args, kwargs, SharedLibraryHolder) + elif default_library == 'static': + return self.build_target(node, args, kwargs, StaticLibraryHolder) + elif default_library == 'both': + return self.build_both_libraries(node, args, kwargs) + else: + raise InterpreterException('Unknown default_library value: %s.', default_library) + def build_target(self, node, args, kwargs, targetholder): if not args: raise InterpreterException('Target does not have a name.') @@ -3246,7 +3401,13 @@ different subdirectory. mlog.debug('Unknown target type:', str(targetholder)) raise RuntimeError('Unreachable code') self.kwarg_strings_to_includedirs(kwargs) + + # Filter out kwargs from other target types. For example 'soversion' + # passed to library() when default_library == 'static'. + kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs} + target = targetclass(name, self.subdir, self.subproject, is_cross, sources, objs, self.environment, kwargs) + if is_cross: self.add_cross_stdlib_info(target) l = targetholder(target, self) diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 9279506..f957d90 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -105,6 +105,9 @@ class InvalidCode(InterpreterException): class InvalidArguments(InterpreterException): pass +class SubdirDoneRequest(BaseException): + pass + class InterpreterObject: def __init__(self): self.methods = {} @@ -203,6 +206,8 @@ class InterpreterBase: try: self.current_lineno = cur.lineno self.evaluate_statement(cur) + except SubdirDoneRequest: + break except Exception as e: if not(hasattr(e, 'lineno')): e.lineno = cur.lineno diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 2333e27..cb07c5e 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -13,9 +13,14 @@ # limitations under the License. from .mesonlib import Popen_safe +from . import mesonlib class StaticLinker: - pass + def can_linker_accept_rsp(self): + """ + Determines whether the linker can accept arguments using the @rsp syntax. + """ + return mesonlib.is_windows() class VisualStudioLinker(StaticLinker): @@ -51,6 +56,9 @@ class VisualStudioLinker(StaticLinker): def thread_link_flags(self, env): return [] + def openmp_flags(self): + return [] + def get_option_link_args(self, options): return [] @@ -75,6 +83,12 @@ class ArLinker(StaticLinker): self.std_args = ['csrD'] else: self.std_args = ['csr'] + # For 'armar' the options should be prefixed with '-'. + if 'armar' in stdo: + self.std_args = ['-csr'] + + def can_linker_accept_rsp(self): + return False def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): return [] @@ -103,6 +117,9 @@ class ArLinker(StaticLinker): def thread_link_flags(self, env): return [] + def openmp_flags(self): + return [] + def get_option_link_args(self, options): return [] diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index b409615..9a11332 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -17,13 +17,29 @@ import sys import argparse from . import (coredata, mesonlib, build) -parser = argparse.ArgumentParser(prog='meson configure') +def buildparser(): + parser = argparse.ArgumentParser(prog='meson configure') + coredata.register_builtin_arguments(parser) -parser.add_argument('-D', action='append', default=[], dest='sets', - help='Set an option to the given value.') -parser.add_argument('directory', nargs='*') -parser.add_argument('--clearcache', action='store_true', default=False, - help='Clear cached state (e.g. found dependencies)') + parser.add_argument('-D', action='append', default=[], dest='sets', + help='Set an option to the given value.') + parser.add_argument('directory', nargs='*') + parser.add_argument('--clearcache', action='store_true', default=False, + help='Clear cached state (e.g. found dependencies)') + return parser + + +def filter_builtin_options(args, original_args): + """Filter out any args passed with -- instead of -D.""" + for arg in original_args: + if not arg.startswith('--') or arg == '--clearcache': + continue + name = arg.lstrip('--').split('=', 1)[0] + if any([a.startswith(name + '=') for a in args.sets]): + raise mesonlib.MesonException( + 'Got argument {0} as both -D{0} and --{0}. Pick one.'.format(name)) + args.sets.append('{}={}'.format(name, getattr(args, name))) + delattr(args, name) class ConfException(mesonlib.MesonException): @@ -226,7 +242,8 @@ def run(args): args = mesonlib.expand_arguments(args) if not args: args = [os.getcwd()] - options = parser.parse_args(args) + options = buildparser().parse_args(args) + filter_builtin_options(options, args) if len(options.directory) > 1: print('%s <build directory>' % args[0]) print('If you omit the build directory, the current directory is substituted.') diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index a076e3e..8a2b67c 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -541,8 +541,13 @@ def has_path_sep(name, sep='/\\'): return True return False -def do_replacement(regex, line, confdata): +def do_replacement(regex, line, format, confdata): missing_variables = set() + start_tag = '@' + backslash_tag = '\\@' + if format == 'cmake': + start_tag = '${' + backslash_tag = '\\${' def variable_replace(match): # Pairs of escape characters before '@' or '\@' @@ -550,8 +555,8 @@ def do_replacement(regex, line, confdata): num_escapes = match.end(0) - match.start(0) return '\\' * (num_escapes // 2) # Single escape character and '@' - elif match.group(0) == '\\@': - return '@' + elif match.group(0) == backslash_tag: + return start_tag # Template variable to be replaced else: varname = match.group(1) @@ -591,7 +596,7 @@ def do_mesondefine(line, confdata): raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) -def do_conf_file(src, dst, confdata): +def do_conf_file(src, dst, confdata, format): try: with open(src, encoding='utf-8') as f: data = f.readlines() @@ -599,14 +604,24 @@ def do_conf_file(src, dst, confdata): raise MesonException('Could not read input file %s: %s' % (src, str(e))) # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define # Also allow escaping '@' with '\@' - regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + if format in ['meson', 'cmake@']: + regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + elif format == 'cmake': + regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}') + else: + raise MesonException('Format "{}" not handled'.format(format)) + + search_token = '#mesondefine' + if format != 'meson': + search_token = '#cmakedefine' + result = [] missing_variables = set() for line in data: - if line.startswith('#mesondefine'): + if line.startswith(search_token): line = do_mesondefine(line, confdata) else: - line, missing = do_replacement(regex, line, confdata) + line, missing = do_replacement(regex, line, format, confdata) missing_variables.update(missing) result.append(line) dst_tmp = dst + '~' @@ -734,7 +749,9 @@ def expand_arguments(args): return expended_args def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs): - if sys.version_info < (3, 6) or not sys.stdout.encoding: + import locale + encoding = locale.getpreferredencoding() + if sys.version_info < (3, 6) or not sys.stdout.encoding or encoding.upper() != 'UTF-8': return Popen_safe_legacy(args, write=write, stderr=stderr, **kwargs) p = subprocess.Popen(args, universal_newlines=True, close_fds=False, diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 651224e..613e953 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -25,47 +25,9 @@ from .wrap import WrapMode, wraptool default_warning = '1' -def add_builtin_argument(p, name, **kwargs): - k = kwargs.get('dest', name.replace('-', '_')) - c = coredata.get_builtin_option_choices(k) - b = kwargs.get('action', None) in ['store_true', 'store_false'] - h = coredata.get_builtin_option_description(k) - if not b: - h = h.rstrip('.') + ' (default: %s).' % coredata.get_builtin_option_default(k) - if c and not b: - kwargs['choices'] = c - default = coredata.get_builtin_option_default(k, noneIfSuppress=True) - if default is not None: - kwargs['default'] = default - else: - kwargs['default'] = argparse.SUPPRESS - p.add_argument('--' + name, help=h, **kwargs) - def create_parser(): p = argparse.ArgumentParser(prog='meson') - add_builtin_argument(p, 'prefix') - add_builtin_argument(p, 'libdir') - add_builtin_argument(p, 'libexecdir') - add_builtin_argument(p, 'bindir') - add_builtin_argument(p, 'sbindir') - add_builtin_argument(p, 'includedir') - add_builtin_argument(p, 'datadir') - add_builtin_argument(p, 'mandir') - add_builtin_argument(p, 'infodir') - add_builtin_argument(p, 'localedir') - add_builtin_argument(p, 'sysconfdir') - add_builtin_argument(p, 'localstatedir') - add_builtin_argument(p, 'sharedstatedir') - add_builtin_argument(p, 'backend') - add_builtin_argument(p, 'buildtype') - add_builtin_argument(p, 'strip', action='store_true') - add_builtin_argument(p, 'unity') - add_builtin_argument(p, 'werror', action='store_true') - add_builtin_argument(p, 'layout') - add_builtin_argument(p, 'default-library') - add_builtin_argument(p, 'warnlevel', dest='warning_level') - add_builtin_argument(p, 'stdsplit', action='store_false') - add_builtin_argument(p, 'errorlogs', action='store_false') + coredata.register_builtin_arguments(p) p.add_argument('--cross-file', default=None, help='File describing cross compilation environment.') p.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", @@ -87,6 +49,25 @@ def wrapmodetype(string): msg = 'invalid argument {!r}, use one of {}'.format(string, msg) raise argparse.ArgumentTypeError(msg) +def filter_builtin_options(args, original_args): + """Filter out any builtin arguments passed as -D options. + + Error if an argument is passed with -- and -D + """ + arguments = dict(p.split('=', 1) for p in args.projectoptions) + meson_opts = set(arguments).intersection(set(coredata.builtin_options)) + if meson_opts: + for arg in meson_opts: + value = arguments[arg] + if any([a.startswith('--{}'.format(arg)) for a in original_args]): + raise MesonException( + 'Argument "{0}" passed as both --{0} and -D{0}, but only ' + 'one is allowed'.format(arg)) + setattr(args, coredata.get_builtin_option_destination(arg), value) + + # Remove the builtin option from the project args values + args.projectoptions.remove('{}={}'.format(arg, value)) + class MesonApp: def __init__(self, dir1, dir2, script_launcher, handshake, options, original_cmd_line_args): @@ -210,7 +191,8 @@ class MesonApp: # Post-conf scripts must be run after writing coredata or else introspection fails. g.run_postconf_scripts() except: - os.unlink(cdf) + if 'cdf' in locals(): + os.unlink(cdf) raise def run_script_command(args): @@ -337,6 +319,7 @@ def run(original_args, mainfile=None): args = mesonlib.expand_arguments(args) options = parser.parse_args(args) + filter_builtin_options(options, args) args = options.directories if not args or len(args) > 2: # if there's a meson.build in the dir above, and not in the current diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 23e666c..5a9d4cf 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -26,26 +26,28 @@ import argparse import sys, os import pathlib -parser = argparse.ArgumentParser(prog='meson introspect') -parser.add_argument('--targets', action='store_true', dest='list_targets', default=False, - help='List top level targets.') -parser.add_argument('--installed', action='store_true', dest='list_installed', default=False, - help='List all installed files and directories.') -parser.add_argument('--target-files', action='store', dest='target_files', default=None, - help='List source files for a given target.') -parser.add_argument('--buildsystem-files', action='store_true', dest='buildsystem_files', default=False, - help='List files that make up the build system.') -parser.add_argument('--buildoptions', action='store_true', dest='buildoptions', default=False, - help='List all build options.') -parser.add_argument('--tests', action='store_true', dest='tests', default=False, - help='List all unit tests.') -parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', default=False, - help='List all benchmarks.') -parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False, - help='List external dependencies.') -parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False, - help='Information about projects.') -parser.add_argument('builddir', nargs='?', help='The build directory') +def buildparser(): + parser = argparse.ArgumentParser(prog='meson introspect') + parser.add_argument('--targets', action='store_true', dest='list_targets', default=False, + help='List top level targets.') + parser.add_argument('--installed', action='store_true', dest='list_installed', default=False, + help='List all installed files and directories.') + parser.add_argument('--target-files', action='store', dest='target_files', default=None, + help='List source files for a given target.') + parser.add_argument('--buildsystem-files', action='store_true', dest='buildsystem_files', default=False, + help='List files that make up the build system.') + parser.add_argument('--buildoptions', action='store_true', dest='buildoptions', default=False, + help='List all build options.') + parser.add_argument('--tests', action='store_true', dest='tests', default=False, + help='List all unit tests.') + parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', default=False, + help='List all benchmarks.') + parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False, + help='List external dependencies.') + parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False, + help='Information about projects.') + parser.add_argument('builddir', nargs='?', default='.', help='The build directory') + return parser def determine_installed_path(target, installdata): install_target = None @@ -202,7 +204,7 @@ def list_projinfo(builddata): def run(args): datadir = 'meson-private' - options = parser.parse_args(args) + options = buildparser().parse_args(args) if options.builddir is not None: datadir = os.path.join(options.builddir, datadir) if not os.path.isdir(datadir): diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index 347cede..6cbaf60 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -13,6 +13,7 @@ # limitations under the License. import sys, os, platform, io +from contextlib import contextmanager """This is (mostly) a standalone module used to write logging information about Meson runs. Some output goes to screen, @@ -25,6 +26,7 @@ else: log_dir = None log_file = None log_fname = 'meson-log.txt' +log_depth = 0 def initialize(logdir): global log_dir, log_file @@ -77,15 +79,21 @@ def process_markup(args, keep): return arr def force_print(*args, **kwargs): + iostr = io.StringIO() + kwargs['file'] = iostr + print(*args, **kwargs) + + raw = iostr.getvalue() + if log_depth > 0: + prepend = '|' * log_depth + raw = prepend + raw.replace('\n', '\n' + prepend, raw.count('\n') - 1) + # _Something_ is going to get printed. try: - print(*args, **kwargs) + print(raw, end='') except UnicodeEncodeError: - iostr = io.StringIO() - kwargs['file'] = iostr - print(*args, **kwargs) - cleaned = iostr.getvalue().encode('ascii', 'replace').decode('ascii') - print(cleaned) + cleaned = raw.encode('ascii', 'replace').decode('ascii') + print(cleaned, end='') def debug(*args, **kwargs): arr = process_markup(args, False) @@ -146,3 +154,12 @@ def format_list(list): return list[0] else: return '' + +@contextmanager +def nested(): + global log_depth + log_depth += 1 + try: + yield + finally: + log_depth -= 1 diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index bf513fd..55bbbd3 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -18,26 +18,15 @@ class permittedSnippetKwargs: return f(s, interpreter, state, args, kwargs) return wrapped -_found_programs = {} - class ExtensionModule: - def __init__(self): + def __init__(self, interpreter): + self.interpreter = interpreter self.snippets = set() # List of methods that operate only on the interpreter. def is_snippet(self, funcname): return funcname in self.snippets -def find_program(program_name, target_name): - if program_name in _found_programs: - return _found_programs[program_name] - program = dependencies.ExternalProgram(program_name) - if not program.found(): - m = "Target {!r} can't be generated as {!r} could not be found" - raise MesonException(m.format(target_name, program_name)) - _found_programs[program_name] = program - return program - def get_include_args(include_dirs, prefix='-I'): ''' diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 8b6397e..5455118 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -25,7 +25,7 @@ from .. import mesonlib from .. import compilers from .. import interpreter from . import GResourceTarget, GResourceHeaderTarget, GirTarget, TypelibTarget, VapiTarget -from . import find_program, get_include_args +from . import get_include_args from . import ExtensionModule from . import ModuleReturnValue from ..mesonlib import MesonException, OrderedSet, Popen_safe, extract_as_list @@ -45,14 +45,14 @@ gdbuswarning_printed = False gresource_warning_printed = False _gir_has_extra_lib_arg = None -def gir_has_extra_lib_arg(): +def gir_has_extra_lib_arg(intr_obj): global _gir_has_extra_lib_arg if _gir_has_extra_lib_arg is not None: return _gir_has_extra_lib_arg _gir_has_extra_lib_arg = False try: - g_ir_scanner = find_program('g-ir-scanner', '').get_command() + g_ir_scanner = intr_obj.find_program_impl('g-ir-scanner').get_command() opts = Popen_safe(g_ir_scanner + ['--help'], stderr=subprocess.STDOUT)[1] _gir_has_extra_lib_arg = '--extra-library' in opts except (MesonException, FileNotFoundError, subprocess.CalledProcessError): @@ -302,7 +302,7 @@ class GnomeModule(ExtensionModule): link_command.append('-Wl,-rpath,' + libdir) if depends: depends.append(lib) - if gir_has_extra_lib_arg() and use_gir_args: + if gir_has_extra_lib_arg(self.interpreter) and use_gir_args: link_command.append('--extra-library=' + lib.name) else: link_command.append('-l' + lib.name) @@ -369,7 +369,7 @@ class GnomeModule(ExtensionModule): mlog.log('dependency {!r} not handled to build gir files'.format(dep)) continue - if gir_has_extra_lib_arg() and use_gir_args: + if gir_has_extra_lib_arg(self.interpreter) and use_gir_args: fixed_ldflags = OrderedSet() for ldflag in ldflags: if ldflag.startswith("-l"): @@ -388,8 +388,8 @@ class GnomeModule(ExtensionModule): raise MesonException('Gir takes one argument') if kwargs.get('install_dir'): raise MesonException('install_dir is not supported with generate_gir(), see "install_dir_gir" and "install_dir_typelib"') - giscanner = find_program('g-ir-scanner', 'Gir') - gicompiler = find_program('g-ir-compiler', 'Gir') + giscanner = self.interpreter.find_program_impl('g-ir-scanner') + gicompiler = self.interpreter.find_program_impl('g-ir-compiler') girtarget = args[0] while hasattr(girtarget, 'held_object'): girtarget = girtarget.held_object @@ -637,7 +637,7 @@ class GnomeModule(ExtensionModule): srcdir = os.path.join(state.build_to_src, state.subdir) outdir = state.subdir - cmd = [find_program('glib-compile-schemas', 'gsettings-compile')] + cmd = [self.interpreter.find_program_impl('glib-compile-schemas')] cmd += ['--targetdir', outdir, srcdir] kwargs['command'] = cmd kwargs['input'] = [] @@ -792,7 +792,7 @@ This will become a hard error in the future.''') state.backend.get_target_dir(s), s.get_outputs()[0])) elif isinstance(s, mesonlib.File): - content_files.append(s.rel_to_builddir(state.build_to_src)) + content_files.append(os.path.join(state.environment.get_build_dir(), s.subdir, s.fname)) elif isinstance(s, build.GeneratedList): depends.append(s) for gen_src in s.get_outputs(): @@ -869,22 +869,21 @@ This will become a hard error in the future.''') return [] @permittedKwargs({'interface_prefix', 'namespace', 'object_manager', 'build_by_default', - 'annotations', 'docbook'}) + 'annotations', 'docbook', 'install', 'install_header'}) def gdbus_codegen(self, state, args, kwargs): if len(args) != 2: raise MesonException('Gdbus_codegen takes two arguments, name and xml file.') namebase = args[0] xml_file = args[1] target_name = namebase + '-gdbus' - cmd = [find_program('gdbus-codegen', target_name)] + cmd = [self.interpreter.find_program_impl('gdbus-codegen')] if 'interface_prefix' in kwargs: cmd += ['--interface-prefix', kwargs.pop('interface_prefix')] if 'namespace' in kwargs: cmd += ['--c-namespace', kwargs.pop('namespace')] if kwargs.get('object_manager', False): cmd += ['--c-generate-object-manager'] - if 'docbook' in kwargs: - cmd += ['--generate-docbook', kwargs.pop('docbook')] + build_by_default = kwargs.get('build_by_default', False) # Annotations are a bit ugly in that they are a list of lists of strings... annotations = kwargs.pop('annotations', []) @@ -898,21 +897,74 @@ This will become a hard error in the future.''') raise MesonException('Annotations must be made up of 3 strings for ELEMENT, KEY, and VALUE') cmd += ['--annotate'] + annotation - # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a - if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.51.3'): - cmd += ['--output-directory', '@OUTDIR@', '--generate-c-code', namebase, '@INPUT@'] + # https://git.gnome.org/browse/glib/commit/?id=e4d68c7b3e8b01ab1a4231bf6da21d045cb5a816 + if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.55.2'): + targets = [] + install_header = kwargs.get('install_header', False) + install_dir = kwargs.get('install_dir', state.environment.coredata.get_builtin_option('includedir')) + + output = namebase + '.c' + custom_kwargs = {'input': xml_file, + 'output': output, + 'command': cmd + ['--body', '--output', '@OUTDIR@/' + output, '@INPUT@'], + 'build_by_default': build_by_default + } + targets.append(build.CustomTarget(output, state.subdir, state.subproject, custom_kwargs)) + + output = namebase + '.h' + custom_kwargs = {'input': xml_file, + 'output': output, + 'command': cmd + ['--header', '--output', '@OUTDIR@/' + output, '@INPUT@'], + 'build_by_default': build_by_default, + 'install': install_header, + 'install_dir': install_dir + } + targets.append(build.CustomTarget(output, state.subdir, state.subproject, custom_kwargs)) + + if 'docbook' in kwargs: + docbook = kwargs['docbook'] + if not isinstance(docbook, str): + raise MesonException('docbook value must be a string.') + + docbook_cmd = cmd + ['--output-directory', '@OUTDIR@', '--generate-docbook', docbook, '@INPUT@'] + + output = namebase + '-docbook' + custom_kwargs = {'input': xml_file, + 'output': output, + 'command': docbook_cmd, + 'build_by_default': build_by_default + } + targets.append(build.CustomTarget(output, state.subdir, state.subproject, custom_kwargs)) + + objects = targets else: - self._print_gdbus_warning() - cmd += ['--generate-c-code', '@OUTDIR@/' + namebase, '@INPUT@'] - outputs = [namebase + '.c', namebase + '.h'] - custom_kwargs = {'input': xml_file, - 'output': outputs, - 'command': cmd - } - if 'build_by_default' in kwargs: - custom_kwargs['build_by_default'] = kwargs['build_by_default'] - ct = build.CustomTarget(target_name, state.subdir, state.subproject, custom_kwargs) - return ModuleReturnValue(ct, [ct]) + if 'docbook' in kwargs: + docbook = kwargs['docbook'] + if not isinstance(docbook, str): + raise MesonException('docbook value must be a string.') + + cmd += ['--generate-docbook', docbook] + + # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a + if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.51.3'): + cmd += ['--output-directory', '@OUTDIR@', '--generate-c-code', namebase, '@INPUT@'] + else: + self._print_gdbus_warning() + cmd += ['--generate-c-code', '@OUTDIR@/' + namebase, '@INPUT@'] + outputs = [namebase + '.c', namebase + '.h'] + custom_kwargs = {'input': xml_file, + 'output': outputs, + 'command': cmd, + 'build_by_default': build_by_default + } + ct = build.CustomTarget(target_name, state.subdir, state.subproject, custom_kwargs) + # Ensure that the same number (and order) of arguments are returned + # regardless of the gdbus-codegen (glib) version being used + targets = [ct, ct] + if 'docbook' in kwargs: + targets.append(ct) + objects = [ct] + return ModuleReturnValue(targets, objects) @permittedKwargs({'sources', 'c_template', 'h_template', 'install_header', 'install_dir', 'comments', 'identifier_prefix', 'symbol_prefix', 'eprod', 'vprod', @@ -961,7 +1013,7 @@ This will become a hard error in the future.''') elif arg not in known_custom_target_kwargs: raise MesonException( 'Mkenums does not take a %s keyword argument.' % (arg, )) - cmd = [find_program('glib-mkenums', 'mkenums')] + cmd + cmd = [self.interpreter.find_program_impl(['glib-mkenums', 'mkenums'])] + cmd custom_kwargs = {} for arg in known_custom_target_kwargs: if arg in kwargs: @@ -1046,6 +1098,12 @@ This will become a hard error in the future.''') raise MesonException( 'Sources keyword argument must be a string or array.') + # The `install_header` argument will be used by mkenums() when + # not using template files, so we need to forcibly unset it + # when generating the C source file, otherwise we will end up + # installing it + c_file_kwargs['install_header'] = False + header_prefix = kwargs.get('header_prefix', '') decl_decorator = kwargs.get('decorator', '') func_prefix = kwargs.get('function_prefix', '') @@ -1151,7 +1209,7 @@ G_END_DECLS''' new_genmarshal = mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.53.3') - cmd = [find_program('glib-genmarshal', output + '_genmarshal')] + cmd = [self.interpreter.find_program_impl('glib-genmarshal')] known_kwargs = ['internal', 'nostdinc', 'skip_source', 'stdinc', 'valist_marshallers', 'extra_args'] known_custom_target_kwargs = ['build_always', 'depends', @@ -1297,9 +1355,9 @@ G_END_DECLS''' pkg_cmd, vapi_depends, vapi_packages, vapi_includes = self._extract_vapi_packages(state, kwargs) target_name = 'generate_vapi({})'.format(library) if 'VAPIGEN' in os.environ: - cmd = [find_program(os.environ['VAPIGEN'], target_name)] + cmd = [self.interpreter.find_program_impl(os.environ['VAPIGEN'])] else: - cmd = [find_program('vapigen', target_name)] + cmd = [self.interpreter.find_program_impl('vapigen')] cmd += ['--quiet', '--library=' + library, '--directory=' + build_dir] cmd += self._vapi_args_to_command('--vapidir=', 'vapi_dirs', kwargs) cmd += self._vapi_args_to_command('--metadatadir=', 'metadata_dirs', kwargs) @@ -1354,5 +1412,5 @@ G_END_DECLS''' created_values.append(rv) return ModuleReturnValue(rv, created_values) -def initialize(): - return GnomeModule() +def initialize(*args, **kwargs): + return GnomeModule(*args, **kwargs) diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 6c02fbb..4281200 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -143,5 +143,5 @@ class I18nModule(ExtensionModule): return ModuleReturnValue(None, targets) -def initialize(): - return I18nModule() +def initialize(*args, **kwargs): + return I18nModule(*args, **kwargs) diff --git a/mesonbuild/modules/modtest.py b/mesonbuild/modules/modtest.py index 758eeae..533989f 100644 --- a/mesonbuild/modules/modtest.py +++ b/mesonbuild/modules/modtest.py @@ -24,5 +24,5 @@ class TestModule(ExtensionModule): rv = ModuleReturnValue(None, []) return rv -def initialize(): - return TestModule() +def initialize(*args, **kwargs): + return TestModule(*args, **kwargs) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index c89f657..934e2f4 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -31,6 +31,7 @@ class DependenciesHelper: self.priv_libs = [] self.priv_reqs = [] self.cflags = [] + self.version_reqs = {} def add_pub_libs(self, libs): libs, reqs, cflags = self._process_libs(libs, True) @@ -57,12 +58,17 @@ class DependenciesHelper: processed_reqs.append(obj.generated_pc) elif hasattr(obj, 'pcdep'): pcdeps = mesonlib.listify(obj.pcdep) - processed_reqs += [i.name for i in pcdeps] + for d in pcdeps: + processed_reqs += d.name + self.add_version_reqs(d.name, obj.version_reqs) elif isinstance(obj, dependencies.PkgConfigDependency): if obj.found(): processed_reqs.append(obj.name) + self.add_version_reqs(obj.name, obj.version_reqs) elif isinstance(obj, str): - processed_reqs.append(obj) + name, version_req = self.split_version_req(obj) + processed_reqs.append(name) + self.add_version_reqs(name, version_req) elif isinstance(obj, dependencies.Dependency) and not obj.found(): pass else: @@ -81,14 +87,18 @@ class DependenciesHelper: processed_reqs = [] processed_cflags = [] for obj in libs: + shared_library_only = getattr(obj, 'shared_library_only', False) if hasattr(obj, 'pcdep'): pcdeps = mesonlib.listify(obj.pcdep) - processed_reqs += [i.name for i in pcdeps] + for d in pcdeps: + processed_reqs.append(d.name) + self.add_version_reqs(d.name, obj.version_reqs) elif hasattr(obj, 'generated_pc'): processed_reqs.append(obj.generated_pc) elif isinstance(obj, dependencies.PkgConfigDependency): if obj.found(): processed_reqs.append(obj.name) + self.add_version_reqs(obj.name, obj.version_reqs) elif isinstance(obj, dependencies.ThreadDependency): processed_libs += obj.get_compiler().thread_link_flags(obj.env) processed_cflags += obj.get_compiler().thread_flags(obj.env) @@ -96,24 +106,26 @@ class DependenciesHelper: if obj.found(): processed_libs += obj.get_link_args() processed_cflags += obj.get_compile_args() - elif isinstance(obj, build.SharedLibrary): + elif isinstance(obj, build.SharedLibrary) and shared_library_only: + # Do not pull dependencies for shared libraries because they are + # only required for static linking. Adding private requires has + # the side effect of exposing their cflags, which is the + # intended behaviour of pkg-config but force Debian to add more + # than needed build deps. + # See https://bugs.freedesktop.org/show_bug.cgi?id=105572 processed_libs.append(obj) if public: if not hasattr(obj, 'generated_pc'): obj.generated_pc = self.name - elif isinstance(obj, build.StaticLibrary): - # Due to a "feature" in pkgconfig, it leaks out private dependencies. - # Thus we will not add them to the pc file unless the target - # we are processing is a static library. - # - # This way (hopefully) "pkgconfig --libs --static foobar" works - # and "pkgconfig --cflags/--libs foobar" does not have any trace - # of dependencies that the build file creator has not explicitly - # added to the dependency list. + elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)): processed_libs.append(obj) if public: if not hasattr(obj, 'generated_pc'): obj.generated_pc = self.name + if isinstance(obj, build.StaticLibrary) and public: + self.add_pub_libs(obj.get_dependencies()) + self.add_pub_libs(obj.get_external_deps()) + else: self.add_priv_libs(obj.get_dependencies()) self.add_priv_libs(obj.get_external_deps()) elif isinstance(obj, str): @@ -123,12 +135,51 @@ class DependenciesHelper: return processed_libs, processed_reqs, processed_cflags + def add_version_reqs(self, name, version_reqs): + if version_reqs: + if name not in self.version_reqs: + self.version_reqs[name] = set() + # We could have '>=1.0' or '>= 1.0', remove spaces to normalize + new_vreqs = [s.replace(' ', '') for s in mesonlib.stringlistify(version_reqs)] + self.version_reqs[name].update(new_vreqs) + + def split_version_req(self, s): + for op in ['>=', '<=', '!=', '==', '=', '>', '<']: + pos = s.find(op) + if pos > 0: + return s[0:pos].strip(), s[pos:].strip() + return s, None + + def format_vreq(self, vreq): + # vreq are '>=1.0' and pkgconfig wants '>= 1.0' + for op in ['>=', '<=', '!=', '==', '=', '>', '<']: + if vreq.startswith(op): + return op + ' ' + vreq[len(op):] + return vreq + + def format_reqs(self, reqs): + result = [] + for name in reqs: + vreqs = self.version_reqs.get(name, None) + if vreqs: + result += [name + ' ' + self.format_vreq(vreq) for vreq in vreqs] + else: + result += [name] + return ', '.join(result) + def remove_dups(self): - self.pub_libs = list(set(self.pub_libs)) - self.pub_reqs = list(set(self.pub_reqs)) - self.priv_libs = list(set(self.priv_libs)) - self.priv_reqs = list(set(self.priv_reqs)) - self.cflags = list(set(self.cflags)) + def _fn(xs): + # Remove duplicates whilst preserving original order + result = [] + for x in xs: + if x not in result: + result.append(x) + return result + self.pub_libs = _fn(self.pub_libs) + self.pub_reqs = _fn(self.pub_reqs) + self.priv_libs = _fn(self.priv_libs) + self.priv_reqs = _fn(self.priv_reqs) + self.cflags = _fn(self.cflags) # Remove from private libs/reqs if they are in public already self.priv_libs = [i for i in self.priv_libs if i not in self.pub_libs] @@ -200,11 +251,12 @@ class PkgConfigModule(ExtensionModule): if len(url) > 0: ofile.write('URL: %s\n' % url) ofile.write('Version: %s\n' % version) - if len(deps.pub_reqs) > 0: - ofile.write('Requires: {}\n'.format(' '.join(deps.pub_reqs))) - if len(deps.priv_reqs) > 0: - ofile.write( - 'Requires.private: {}\n'.format(' '.join(deps.priv_reqs))) + reqs_str = deps.format_reqs(deps.pub_reqs) + if len(reqs_str) > 0: + ofile.write('Requires: {}\n'.format(reqs_str)) + reqs_str = deps.format_reqs(deps.priv_reqs) + if len(reqs_str) > 0: + ofile.write('Requires.private: {}\n'.format(reqs_str)) if len(conflicts) > 0: ofile.write('Conflicts: {}\n'.format(' '.join(conflicts))) @@ -254,20 +306,34 @@ class PkgConfigModule(ExtensionModule): 'subdirs', 'requires', 'requires_private', 'libraries_private', 'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions'}) def generate(self, state, args, kwargs): - if len(args) > 0: - raise mesonlib.MesonException('Pkgconfig_gen takes no positional arguments.') + default_version = state.project_version['version'] + default_install_dir = None + default_description = None + default_name = None + mainlib = None + if len(args) == 1: + mainlib = getattr(args[0], 'held_object', args[0]) + if not isinstance(mainlib, (build.StaticLibrary, build.SharedLibrary)): + raise mesonlib.MesonException('Pkgconfig_gen first positional argument must be a library object') + default_name = mainlib.name + default_description = state.project_name + ': ' + mainlib.name + install_dir = mainlib.get_custom_install_dir()[0] + if isinstance(install_dir, str): + default_install_dir = os.path.join(install_dir, 'pkgconfig') + elif len(args) > 1: + raise mesonlib.MesonException('Too many positional arguments passed to Pkgconfig_gen.') subdirs = mesonlib.stringlistify(kwargs.get('subdirs', ['.'])) - version = kwargs.get('version', None) + version = kwargs.get('version', default_version) if not isinstance(version, str): raise mesonlib.MesonException('Version must be specified.') - name = kwargs.get('name', None) + name = kwargs.get('name', default_name) if not isinstance(name, str): raise mesonlib.MesonException('Name not specified.') filebase = kwargs.get('filebase', name) if not isinstance(filebase, str): raise mesonlib.MesonException('Filebase must be a string.') - description = kwargs.get('description', None) + description = kwargs.get('description', default_description) if not isinstance(description, str): raise mesonlib.MesonException('Description is not a string.') url = kwargs.get('url', '') @@ -276,6 +342,8 @@ class PkgConfigModule(ExtensionModule): conflicts = mesonlib.stringlistify(kwargs.get('conflicts', [])) deps = DependenciesHelper(filebase) + if mainlib: + deps.add_pub_libs(mainlib) deps.add_pub_libs(kwargs.get('libraries', [])) deps.add_priv_libs(kwargs.get('libraries_private', [])) deps.add_pub_reqs(kwargs.get('requires', [])) @@ -286,7 +354,7 @@ class PkgConfigModule(ExtensionModule): if dversions: compiler = state.environment.coredata.compilers.get('d') if compiler: - deps.add_cflags(compiler.get_feature_args({'versions': dversions})) + deps.add_cflags(compiler.get_feature_args({'versions': dversions}, None)) def parse_variable_list(stringlist): reserved = ['prefix', 'libdir', 'includedir'] @@ -315,7 +383,7 @@ class PkgConfigModule(ExtensionModule): variables = parse_variable_list(mesonlib.stringlistify(kwargs.get('variables', []))) pcfile = filebase + '.pc' - pkgroot = kwargs.get('install_dir', None) + pkgroot = kwargs.get('install_dir', default_install_dir) if pkgroot is None: pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'pkgconfig') if not isinstance(pkgroot, str): @@ -325,5 +393,5 @@ class PkgConfigModule(ExtensionModule): res = build.Data(mesonlib.File(True, state.environment.get_scratch_dir(), pcfile), pkgroot) return ModuleReturnValue(res, [res]) -def initialize(): - return PkgConfigModule() +def initialize(*args, **kwargs): + return PkgConfigModule(*args, **kwargs) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py new file mode 100644 index 0000000..16bdfd6 --- /dev/null +++ b/mesonbuild/modules/python.py @@ -0,0 +1,433 @@ +# Copyright 2018 The Meson development team + +# 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. + +import os +import json + +from pathlib import Path +from .. import mesonlib +from . import ExtensionModule +from mesonbuild.modules import ModuleReturnValue +from . import permittedSnippetKwargs +from ..interpreterbase import ( + noPosargs, noKwargs, permittedKwargs, + InterpreterObject, InvalidArguments +) +from ..interpreter import ExternalProgramHolder +from ..build import known_shmod_kwargs +from .. import mlog +from ..environment import detect_cpu_family +from ..dependencies.base import ( + DependencyMethods, ExternalDependency, + ExternalProgram, PkgConfigDependency, + NonExistingExternalProgram +) + +mod_kwargs = set(['subdir']) +mod_kwargs.update(known_shmod_kwargs) +mod_kwargs -= set(['name_prefix', 'name_suffix']) + + +def run_command(python, command): + _, stdout, _ = mesonlib.Popen_safe(python.get_command() + [ + '-c', + command]) + + return stdout.strip() + + +class PythonDependency(ExternalDependency): + def __init__(self, python_holder, environment, kwargs): + super().__init__('python', environment, None, kwargs) + self.name = 'python' + self.static = kwargs.get('static', False) + self.version = python_holder.version + self.platform = python_holder.platform + self.pkgdep = None + self.variables = python_holder.variables + self.paths = python_holder.paths + if mesonlib.version_compare(self.version, '>= 3.0'): + self.major_version = 3 + else: + self.major_version = 2 + + if DependencyMethods.PKGCONFIG in self.methods: + pkg_version = self.variables.get('LDVERSION') or self.version + pkg_libdir = self.variables.get('LIBPC') + old_pkg_libdir = os.environ.get('PKG_CONFIG_LIBDIR') + old_pkg_path = os.environ.get('PKG_CONFIG_PATH') + + os.environ.pop('PKG_CONFIG_PATH', None) + + if pkg_libdir: + os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir + + try: + self.pkgdep = PkgConfigDependency('python-{}'.format(pkg_version), environment, kwargs) + except Exception: + pass + + if old_pkg_path is not None: + os.environ['PKG_CONFIG_PATH'] = old_pkg_path + + if old_pkg_libdir is not None: + os.environ['PKG_CONFIG_LIBDIR'] = old_pkg_libdir + else: + os.environ.pop('PKG_CONFIG_LIBDIR', None) + + if self.pkgdep and self.pkgdep.found(): + self.compile_args = self.pkgdep.get_compile_args() + self.link_args = self.pkgdep.get_link_args() + self.is_found = True + self.pcdep = self.pkgdep + else: + self.pkgdep = None + + if mesonlib.is_windows() and DependencyMethods.SYSCONFIG in self.methods: + self._find_libpy_windows(environment) + + if self.is_found: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) + else: + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO')) + + def get_windows_python_arch(self): + if self.platform == 'mingw': + pycc = self.variables.get('CC') + if pycc.startswith('x86_64'): + return '64' + elif pycc.startswith(('i686', 'i386')): + return '32' + else: + mlog.log('MinGW Python built with unknown CC {!r}, please file' + 'a bug'.format(pycc)) + return None + elif self.platform == 'win32': + return '32' + elif self.platform in ('win64', 'win-amd64'): + return '64' + mlog.log('Unknown Windows Python platform {!r}'.format(self.platform)) + return None + + def get_windows_link_args(self): + if self.platform.startswith('win'): + vernum = self.variables.get('py_version_nodot') + if self.static: + libname = 'libpython{}.a'.format(vernum) + else: + libname = 'python{}.lib'.format(vernum) + lib = Path(self.variables.get('base')) / 'libs' / libname + elif self.platform == 'mingw': + if self.static: + libname = self.variables.get('LIBRARY') + else: + libname = self.variables.get('LDLIBRARY') + lib = Path(self.variables.get('LIBDIR')) / libname + if not lib.exists(): + mlog.log('Could not find Python3 library {!r}'.format(str(lib))) + return None + return [str(lib)] + + def _find_libpy_windows(self, env): + ''' + Find python3 libraries on Windows and also verify that the arch matches + what we are building for. + ''' + pyarch = self.get_windows_python_arch() + if pyarch is None: + self.is_found = False + return + arch = detect_cpu_family(env.coredata.compilers) + if arch == 'x86': + arch = '32' + elif arch == 'x86_64': + arch = '64' + else: + # We can't cross-compile Python 3 dependencies on Windows yet + mlog.log('Unknown architecture {!r} for'.format(arch), + mlog.bold(self.name)) + self.is_found = False + return + # Pyarch ends in '32' or '64' + if arch != pyarch: + mlog.log('Need', mlog.bold(self.name), 'for {}-bit, but ' + 'found {}-bit'.format(arch, pyarch)) + self.is_found = False + return + # This can fail if the library is not found + largs = self.get_windows_link_args() + if largs is None: + self.is_found = False + return + self.link_args = largs + # Compile args + inc_paths = mesonlib.OrderedSet([ + self.variables.get('INCLUDEPY'), + self.paths.get('include'), + self.paths.get('platinclude')]) + + self.compile_args += ['-I' + path for path in inc_paths if path] + + # https://sourceforge.net/p/mingw-w64/mailman/message/30504611/ + if pyarch == '64' and self.major_version == 2: + self.compile_args += ['-DMS_WIN64'] + + self.is_found = True + + @staticmethod + def get_methods(): + if mesonlib.is_windows(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG] + elif mesonlib.is_osx(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK] + else: + return [DependencyMethods.PKGCONFIG] + + def get_pkgconfig_variable(self, variable_name, kwargs): + if self.pkgdep: + return self.pkgdep.get_pkgconfig_variable(variable_name, kwargs) + else: + return super().get_pkgconfig_variable(variable_name, kwargs) + + +VARIABLES_COMMAND = ''' +import sysconfig +import json + +print (json.dumps (sysconfig.get_config_vars())) +''' + + +PATHS_COMMAND = ''' +import sysconfig +import json + +print (json.dumps(sysconfig.get_paths())) +''' + + +INSTALL_PATHS_COMMAND = ''' +import sysconfig +import json + +print (json.dumps(sysconfig.get_paths(scheme='posix_prefix', vars={'base': '', 'platbase': '', 'installed_base': ''}))) +''' + + +class PythonInstallation(ExternalProgramHolder, InterpreterObject): + def __init__(self, interpreter, python): + InterpreterObject.__init__(self) + ExternalProgramHolder.__init__(self, python) + self.interpreter = interpreter + prefix = self.interpreter.environment.coredata.get_builtin_option('prefix') + self.variables = json.loads(run_command(python, VARIABLES_COMMAND)) + self.paths = json.loads(run_command(python, PATHS_COMMAND)) + install_paths = json.loads(run_command(python, INSTALL_PATHS_COMMAND)) + self.platlib_install_path = os.path.join(prefix, install_paths['platlib'][1:]) + self.purelib_install_path = os.path.join(prefix, install_paths['purelib'][1:]) + self.version = run_command(python, "import sysconfig; print (sysconfig.get_python_version())") + self.platform = run_command(python, "import sysconfig; print (sysconfig.get_platform())") + + @permittedSnippetKwargs(mod_kwargs) + def extension_module(self, interpreter, state, args, kwargs): + if 'subdir' in kwargs and 'install_dir' in kwargs: + raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive') + + if 'subdir' in kwargs: + subdir = kwargs.pop('subdir', '') + if not isinstance(subdir, str): + raise InvalidArguments('"subdir" argument must be a string.') + + kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) + + suffix = self.variables.get('EXT_SUFFIX') or self.variables.get('SO') or self.variables.get('.so') + + # msys2's python3 has "-cpython-36m.dll", we have to be clever + split = suffix.rsplit('.', 1) + suffix = split.pop(-1) + args[0] += ''.join(s for s in split) + + kwargs['name_prefix'] = '' + kwargs['name_suffix'] = suffix + + return interpreter.func_shared_module(None, args, kwargs) + + def dependency(self, interpreter, state, args, kwargs): + dep = PythonDependency(self, interpreter.environment, kwargs) + return interpreter.holderify(dep) + + @permittedSnippetKwargs(['pure', 'subdir']) + def install_sources(self, interpreter, state, args, kwargs): + pure = kwargs.pop('pure', False) + if not isinstance(pure, bool): + raise InvalidArguments('"pure" argument must be a boolean.') + + subdir = kwargs.pop('subdir', '') + if not isinstance(subdir, str): + raise InvalidArguments('"subdir" argument must be a string.') + + if pure: + kwargs['install_dir'] = os.path.join(self.purelib_install_path, subdir) + else: + kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) + + return interpreter.func_install_data(None, args, kwargs) + + @noPosargs + @permittedKwargs(['pure', 'subdir']) + def get_install_dir(self, node, args, kwargs): + pure = kwargs.pop('pure', True) + if not isinstance(pure, bool): + raise InvalidArguments('"pure" argument must be a boolean.') + + subdir = kwargs.pop('subdir', '') + if not isinstance(subdir, str): + raise InvalidArguments('"subdir" argument must be a string.') + + if pure: + res = os.path.join(self.purelib_install_path, subdir) + else: + res = os.path.join(self.platlib_install_path, subdir) + + return ModuleReturnValue(res, []) + + @noPosargs + @noKwargs + def language_version(self, node, args, kwargs): + return ModuleReturnValue(self.version, []) + + @noPosargs + @noKwargs + def found(self, node, args, kwargs): + return ModuleReturnValue(True, []) + + @noKwargs + def get_path(self, node, args, kwargs): + if len(args) != 1: + raise InvalidArguments('get_path takes exactly one positional argument.') + path_name = args[0] + if not isinstance(path_name, str): + raise InvalidArguments('get_path argument must be a string.') + + path = self.paths.get(path_name) + + if path is None: + raise InvalidArguments('{} is not a valid path name'.format(path_name)) + + return ModuleReturnValue(path, []) + + @noKwargs + def get_variable(self, node, args, kwargs): + if len(args) != 1: + raise InvalidArguments('get_variable takes exactly one positional argument.') + var_name = args[0] + if not isinstance(var_name, str): + raise InvalidArguments('get_variable argument must be a string.') + + var = self.variables.get(var_name) + + if var is None: + raise InvalidArguments('{} is not a valid path name'.format(var_name)) + + return ModuleReturnValue(var, []) + + def method_call(self, method_name, args, kwargs): + try: + fn = getattr(self, method_name) + except AttributeError: + raise InvalidArguments('Python object does not have method %s.' % method_name) + + if method_name in ['extension_module', 'dependency', 'install_sources']: + value = fn(self.interpreter, None, args, kwargs) + return self.interpreter.holderify(value) + elif method_name in ['get_variable', 'get_path', 'found', 'language_version', 'get_install_dir']: + value = fn(None, args, kwargs) + return self.interpreter.module_method_callback(value) + else: + raise InvalidArguments('Python object does not have method %s.' % method_name) + + +class PythonModule(ExtensionModule): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.snippets.add('find_installation') + + # https://www.python.org/dev/peps/pep-0397/ + def _get_win_pythonpath(self, name_or_path): + if name_or_path not in ['python2', 'python3']: + return None + ver = {'python2': '-2', 'python3': '-3'}[name_or_path] + cmd = ['py', ver, '-c', "import sysconfig; print(sysconfig.get_config_var('BINDIR'))"] + _, stdout, _ = mesonlib.Popen_safe(cmd) + dir = stdout.strip() + if os.path.exists(dir): + return os.path.join(dir, 'python') + else: + return None + + @permittedSnippetKwargs(['required']) + def find_installation(self, interpreter, state, args, kwargs): + required = kwargs.get('required', True) + if not isinstance(required, bool): + raise InvalidArguments('"required" argument must be a boolean.') + + if len(args) > 1: + raise InvalidArguments('find_installation takes zero or one positional argument.') + + if args: + name_or_path = args[0] + if not isinstance(name_or_path, str): + raise InvalidArguments('find_installation argument must be a string.') + else: + name_or_path = None + + if not name_or_path: + mlog.log("Using meson's python {}".format(mesonlib.python_command)) + python = ExternalProgram('python3', mesonlib.python_command, silent=True) + else: + if mesonlib.is_windows(): + pythonpath = self._get_win_pythonpath(name_or_path) + if pythonpath is not None: + name_or_path = pythonpath + python = ExternalProgram(name_or_path, silent = True) + # Last ditch effort, python2 or python3 can be named python + # on various platforms, let's not give up just yet, if an executable + # named python is available and has a compatible version, let's use + # it + if not python.found() and name_or_path in ['python2', 'python3']: + python = ExternalProgram('python', silent = True) + if python.found(): + version = run_command(python, "import sysconfig; print (sysconfig.get_python_version())") + if not version or \ + name_or_path == 'python2' and mesonlib.version_compare(version, '>= 3.0') or \ + name_or_path == 'python3' and not mesonlib.version_compare(version, '>= 3.0'): + python = NonExistingExternalProgram() + + if not python.found(): + if required: + raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python')) + res = ExternalProgramHolder(NonExistingExternalProgram()) + else: + # Sanity check, we expect to have something that at least quacks in tune + version = run_command(python, "import sysconfig; print (sysconfig.get_python_version())") + if not version: + res = ExternalProgramHolder(NonExistingExternalProgram()) + else: + res = PythonInstallation(interpreter, python) + + return res + + +def initialize(*args, **kwargs): + return PythonModule(*args, **kwargs) diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index 9fd9f80..79da29a 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -19,18 +19,15 @@ from . import ExtensionModule from mesonbuild.modules import ModuleReturnValue from . import permittedSnippetKwargs from ..interpreterbase import noKwargs -from ..interpreter import shlib_kwargs - -mod_kwargs = set() -mod_kwargs.update(shlib_kwargs) +from ..build import known_shmod_kwargs class Python3Module(ExtensionModule): - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.snippets.add('extension_module') - @permittedSnippetKwargs(mod_kwargs) + @permittedSnippetKwargs(known_shmod_kwargs) def extension_module(self, interpreter, state, args, kwargs): if 'name_prefix' in kwargs: raise mesonlib.MesonException('Name_prefix is set automatically, specifying it is forbidden.') @@ -72,5 +69,5 @@ class Python3Module(ExtensionModule): return ModuleReturnValue(path, []) -def initialize(): - return Python3Module() +def initialize(*args, **kwargs): + return Python3Module(*args, **kwargs) diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index f5ce1ed..39c65ed 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -15,7 +15,7 @@ import os from .. import mlog from .. import build -from ..mesonlib import MesonException, Popen_safe, extract_as_list +from ..mesonlib import MesonException, Popen_safe, extract_as_list, File from ..dependencies import Qt4Dependency, Qt5Dependency import xml.etree.ElementTree as ET from . import ModuleReturnValue, get_include_args @@ -71,19 +71,47 @@ class QtBaseModule: mlog.log(' {}:'.format(compiler_name.lower()), mlog.red('NO')) self.tools_detected = True - def parse_qrc(self, state, fname): - abspath = os.path.join(state.environment.source_dir, state.subdir, fname) - relative_part = os.path.dirname(fname) + def parse_qrc(self, state, rcc_file): + if type(rcc_file) is str: + abspath = os.path.join(state.environment.source_dir, state.subdir, rcc_file) + rcc_dirname = os.path.dirname(abspath) + elif type(rcc_file) is File: + abspath = rcc_file.absolute_path(state.environment.source_dir, state.environment.build_dir) + rcc_dirname = os.path.dirname(abspath) + try: tree = ET.parse(abspath) root = tree.getroot() result = [] for child in root[0]: if child.tag != 'file': - mlog.warning("malformed rcc file: ", os.path.join(state.subdir, fname)) + mlog.warning("malformed rcc file: ", os.path.join(state.subdir, rcc_file)) break else: - result.append(os.path.join(relative_part, child.text)) + resource_path = child.text + # We need to guess if the pointed resource is: + # a) in build directory -> implies a generated file + # b) in source directory + # c) somewhere else external dependency file to bundle + # + # Also from qrc documentation: relative path are always from qrc file + # So relative path must always be computed from qrc file ! + if os.path.isabs(resource_path): + # a) + if resource_path.startswith(os.path.abspath(state.environment.build_dir)): + resource_relpath = os.path.relpath(resource_path, state.environment.build_dir) + result.append(File(is_built=True, subdir='', fname=resource_relpath)) + # either b) or c) + else: + result.append(File(is_built=False, subdir=state.subdir, fname=resource_path)) + else: + path_from_rcc = os.path.normpath(os.path.join(rcc_dirname, resource_path)) + # a) + if path_from_rcc.startswith(state.environment.build_dir): + result.append(File(is_built=True, subdir=state.subdir, fname=resource_path)) + # b) + else: + result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc)) return result except Exception: return [] @@ -102,11 +130,11 @@ class QtBaseModule: if len(rcc_files) > 0: if not self.rcc.found(): raise MesonException(err_msg.format('RCC', 'rcc-qt{}'.format(self.qt_version), self.qt_version)) - qrc_deps = [] - for i in rcc_files: - qrc_deps += self.parse_qrc(state, i) # custom output name set? -> one output file, multiple otherwise if len(args) > 0: + qrc_deps = [] + for i in rcc_files: + qrc_deps += self.parse_qrc(state, i) name = args[0] rcc_kwargs = {'input': rcc_files, 'output': name + '.cpp', @@ -116,7 +144,11 @@ class QtBaseModule: sources.append(res_target) else: for rcc_file in rcc_files: - basename = os.path.basename(rcc_file) + qrc_deps = self.parse_qrc(state, rcc_file) + if type(rcc_file) is str: + basename = os.path.basename(rcc_file) + elif type(rcc_file) is File: + basename = os.path.basename(rcc_file.fname) name = 'qt' + str(self.qt_version) + '-' + basename.replace('.', '_') rcc_kwargs = {'input': rcc_file, 'output': name + '.cpp', diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 3011de7..29992d5 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -19,11 +19,11 @@ from . import ExtensionModule class Qt4Module(ExtensionModule, QtBaseModule): - def __init__(self): + def __init__(self, interpreter): QtBaseModule.__init__(self, qt_version=4) - ExtensionModule.__init__(self) + ExtensionModule.__init__(self, interpreter) -def initialize(): +def initialize(*args, **kwargs): mlog.warning('rcc dependencies will not work properly until this upstream issue is fixed:', mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) - return Qt4Module() + return Qt4Module(*args, **kwargs) diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 7b7acbb..19623ac 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -19,11 +19,11 @@ from . import ExtensionModule class Qt5Module(ExtensionModule, QtBaseModule): - def __init__(self): + def __init__(self, interpreter): QtBaseModule.__init__(self, qt_version=5) - ExtensionModule.__init__(self) + ExtensionModule.__init__(self, interpreter) -def initialize(): +def initialize(*args, **kwargs): mlog.warning('rcc dependencies will not work reliably until this upstream issue is fixed:', mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) - return Qt5Module() + return Qt5Module(*args, **kwargs) diff --git a/mesonbuild/modules/rpm.py b/mesonbuild/modules/rpm.py index dbb01f7..ba5bcaa 100644 --- a/mesonbuild/modules/rpm.py +++ b/mesonbuild/modules/rpm.py @@ -32,10 +32,17 @@ class RPMModule(ExtensionModule): def generate_spec_template(self, state, args, kwargs): compiler_deps = set() for compiler in state.compilers.values(): + # Elbrus has one 'lcc' package for every compiler if isinstance(compiler, compilers.GnuCCompiler): compiler_deps.add('gcc') elif isinstance(compiler, compilers.GnuCPPCompiler): compiler_deps.add('gcc-c++') + elif isinstance(compiler, compilers.ElbrusCCompiler): + compiler_deps.add('lcc') + elif isinstance(compiler, compilers.ElbrusCPPCompiler): + compiler_deps.add('lcc') + elif isinstance(compiler, compilers.ElbrusFortranCompiler): + compiler_deps.add('lcc') elif isinstance(compiler, compilers.ValaCompiler): compiler_deps.add('vala') elif isinstance(compiler, compilers.GnuFortranCompiler): @@ -160,5 +167,5 @@ class RPMModule(ExtensionModule): mlog.log('RPM spec template written to %s.spec.\n' % proj) return ModuleReturnValue(None, []) -def initialize(): - return RPMModule() +def initialize(*args, **kwargs): + return RPMModule(*args, **kwargs) diff --git a/mesonbuild/modules/unstable_icestorm.py b/mesonbuild/modules/unstable_icestorm.py index 1f548b6..55c647f 100644 --- a/mesonbuild/modules/unstable_icestorm.py +++ b/mesonbuild/modules/unstable_icestorm.py @@ -18,17 +18,17 @@ from . import ExtensionModule class IceStormModule(ExtensionModule): - def __init__(self): - super().__init__() + def __init__(self, interpreter): + super().__init__(interpreter) self.snippets.add('project') self.yosys_bin = None def detect_binaries(self, interpreter): - self.yosys_bin = interpreter.func_find_program(None, ['yosys'], {}) - self.arachne_bin = interpreter.func_find_program(None, ['arachne-pnr'], {}) - self.icepack_bin = interpreter.func_find_program(None, ['icepack'], {}) - self.iceprog_bin = interpreter.func_find_program(None, ['iceprog'], {}) - self.icetime_bin = interpreter.func_find_program(None, ['icetime'], {}) + self.yosys_bin = interpreter.find_program_impl(['yosys']) + self.arachne_bin = interpreter.find_program_impl(['arachne-pnr']) + self.icepack_bin = interpreter.find_program_impl(['icepack']) + self.iceprog_bin = interpreter.find_program_impl(['iceprog']) + self.icetime_bin = interpreter.find_program_impl(['icetime']) def project(self, interpreter, state, args, kwargs): if not self.yosys_bin: @@ -80,5 +80,5 @@ class IceStormModule(ExtensionModule): interpreter.func_run_target(None, [time_name], { 'command': [self.icetime_bin, bin_target]}) -def initialize(): - return IceStormModule() +def initialize(*args, **kwargs): + return IceStormModule(*args, **kwargs) diff --git a/mesonbuild/modules/unstable_simd.py b/mesonbuild/modules/unstable_simd.py index b774cff..c41e96c 100644 --- a/mesonbuild/modules/unstable_simd.py +++ b/mesonbuild/modules/unstable_simd.py @@ -18,8 +18,8 @@ from . import ExtensionModule class SimdModule(ExtensionModule): - def __init__(self): - super().__init__() + def __init__(self, interpreter): + super().__init__(interpreter) self.snippets.add('check') # FIXME add Altivec and AVX512. self.isets = ('mmx', @@ -79,5 +79,5 @@ class SimdModule(ExtensionModule): result.append(interpreter.func_static_lib(None, [libname], lib_kwargs)) return [result, cdata] -def initialize(): - return SimdModule() +def initialize(*args, **kwargs): + return SimdModule(*args, **kwargs) diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index dc6e9d8..62cb9d1 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -104,5 +104,5 @@ class WindowsModule(ExtensionModule): return ModuleReturnValue(res_targets, [res_targets]) -def initialize(): - return WindowsModule() +def initialize(*args, **kwargs): + return WindowsModule(*args, **kwargs) diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 0e7524c..9e43065 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -13,9 +13,44 @@ # limitations under the License. import re +import codecs from .mesonlib import MesonException from . import mlog +# This is the regex for the supported escape sequences of a regular string +# literal, like 'abc\x00' +ESCAPE_SEQUENCE_SINGLE_RE = re.compile(r''' + ( \\U........ # 8-digit hex escapes + | \\u.... # 4-digit hex escapes + | \\x.. # 2-digit hex escapes + | \\[0-7]{1,3} # Octal escapes + | \\N\{[^}]+\} # Unicode characters by name + | \\[\\'abfnrtv] # Single-character escapes + )''', re.UNICODE | re.VERBOSE) + +# This is the regex for the supported escape sequences of a multiline string +# literal, like '''abc\x00'''. The only difference is that single quote (') +# doesn't require escaping. +ESCAPE_SEQUENCE_MULTI_RE = re.compile(r''' + ( \\U........ # 8-digit hex escapes + | \\u.... # 4-digit hex escapes + | \\x.. # 2-digit hex escapes + | \\[0-7]{1,3} # Octal escapes + | \\N\{[^}]+\} # Unicode characters by name + | \\[\\abfnrtv] # Single-character escapes + )''', re.UNICODE | re.VERBOSE) + +class MesonUnicodeDecodeError(MesonException): + def __init__(self, match): + super().__init__("%s" % match) + self.match = match + +def decode_match(match): + try: + return codecs.decode(match.group(0), 'unicode_escape') + except UnicodeDecodeError as err: + raise MesonUnicodeDecodeError(match.group(0)) + class ParseException(MesonException): def __init__(self, text, line, lineno, colno): # Format as error message, followed by the line with the error, followed by a caret to show the error column. @@ -112,7 +147,6 @@ class Lexer: par_count = 0 bracket_count = 0 col = 0 - newline_rx = re.compile(r'(?<!\\)((?:\\\\)*)\\n') while loc < len(self.code): matched = False value = None @@ -145,12 +179,18 @@ class Lexer: if match_text.find("\n") != -1: mlog.warning("""Newline character in a string detected, use ''' (three single quotes) for multiline strings instead. This will become a hard error in a future Meson release.""", self.getline(line_start), lineno, col) - value = match_text[1:-1].replace(r"\'", "'") - value = newline_rx.sub(r'\1\n', value) - value = value.replace(r" \\ ".strip(), r" \ ".strip()) + value = match_text[1:-1] + try: + value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value) + except MesonUnicodeDecodeError as err: + raise MesonException("Failed to parse escape sequence: '{}' in string:\n {}".format(err.match, match_text)) elif tid == 'multiline_string': tid = 'string' value = match_text[3:-3] + try: + value = ESCAPE_SEQUENCE_MULTI_RE.sub(decode_match, value) + except MesonUnicodeDecodeError as err: + raise MesonException("Failed to parse escape sequence: '{}' in string:\n{}".format(err.match, match_text)) lines = match_text.split('\n') if len(lines) > 1: lineno += len(lines) - 1 diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 4ed80b1..110a94e 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -29,6 +29,7 @@ import platform import signal import random from copy import deepcopy +import enum # GNU autotools interprets a return code of 77 from tests it executes to # mean that the test should be skipped. @@ -59,56 +60,68 @@ def determine_worker_count(): num_workers = 1 return num_workers -parser = argparse.ArgumentParser(prog='meson test') -parser.add_argument('--repeat', default=1, dest='repeat', type=int, - help='Number of times to run the tests.') -parser.add_argument('--no-rebuild', default=False, action='store_true', - help='Do not rebuild before running tests.') -parser.add_argument('--gdb', default=False, dest='gdb', action='store_true', - help='Run test under gdb.') -parser.add_argument('--list', default=False, dest='list', action='store_true', - help='List available tests.') -parser.add_argument('--wrapper', default=None, dest='wrapper', type=shlex.split, - help='wrapper to run tests with (e.g. Valgrind)') -parser.add_argument('-C', default='.', dest='wd', - help='directory to cd into before running') -parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE', - help='Only run tests belonging to the given suite.') -parser.add_argument('--no-suite', default=[], dest='exclude_suites', action='append', metavar='SUITE', - help='Do not run tests belonging to the given suite.') -parser.add_argument('--no-stdsplit', default=True, dest='split', action='store_false', - help='Do not split stderr and stdout in test logs.') -parser.add_argument('--print-errorlogs', default=False, action='store_true', - help="Whether to print failing tests' logs.") -parser.add_argument('--benchmark', default=False, action='store_true', - help="Run benchmarks instead of tests.") -parser.add_argument('--logbase', default='testlog', - help="Base name for log file.") -parser.add_argument('--num-processes', default=determine_worker_count(), type=int, - help='How many parallel processes to use.') -parser.add_argument('-v', '--verbose', default=False, action='store_true', - help='Do not redirect stdout and stderr') -parser.add_argument('-q', '--quiet', default=False, action='store_true', - help='Produce less output to the terminal.') -parser.add_argument('-t', '--timeout-multiplier', type=float, default=None, - help='Define a multiplier for test timeout, for example ' - ' when running tests in particular conditions they might take' - ' more time to execute.') -parser.add_argument('--setup', default=None, dest='setup', - help='Which test setup to use.') -parser.add_argument('--test-args', default=[], type=shlex.split, - help='Arguments to pass to the specified test(s) or all tests') -parser.add_argument('args', nargs='*', - help='Optional list of tests to run') +def buildparser(): + parser = argparse.ArgumentParser(prog='meson test') + parser.add_argument('--repeat', default=1, dest='repeat', type=int, + help='Number of times to run the tests.') + parser.add_argument('--no-rebuild', default=False, action='store_true', + help='Do not rebuild before running tests.') + parser.add_argument('--gdb', default=False, dest='gdb', action='store_true', + help='Run test under gdb.') + parser.add_argument('--list', default=False, dest='list', action='store_true', + help='List available tests.') + parser.add_argument('--wrapper', default=None, dest='wrapper', type=shlex.split, + help='wrapper to run tests with (e.g. Valgrind)') + parser.add_argument('-C', default='.', dest='wd', + help='directory to cd into before running') + parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE', + help='Only run tests belonging to the given suite.') + parser.add_argument('--no-suite', default=[], dest='exclude_suites', action='append', metavar='SUITE', + help='Do not run tests belonging to the given suite.') + parser.add_argument('--no-stdsplit', default=True, dest='split', action='store_false', + help='Do not split stderr and stdout in test logs.') + parser.add_argument('--print-errorlogs', default=False, action='store_true', + help="Whether to print failing tests' logs.") + parser.add_argument('--benchmark', default=False, action='store_true', + help="Run benchmarks instead of tests.") + parser.add_argument('--logbase', default='testlog', + help="Base name for log file.") + parser.add_argument('--num-processes', default=determine_worker_count(), type=int, + help='How many parallel processes to use.') + parser.add_argument('-v', '--verbose', default=False, action='store_true', + help='Do not redirect stdout and stderr') + parser.add_argument('-q', '--quiet', default=False, action='store_true', + help='Produce less output to the terminal.') + parser.add_argument('-t', '--timeout-multiplier', type=float, default=None, + help='Define a multiplier for test timeout, for example ' + ' when running tests in particular conditions they might take' + ' more time to execute.') + parser.add_argument('--setup', default=None, dest='setup', + help='Which test setup to use.') + parser.add_argument('--test-args', default=[], type=shlex.split, + help='Arguments to pass to the specified test(s) or all tests') + parser.add_argument('args', nargs='*', + help='Optional list of tests to run') + return parser class TestException(mesonlib.MesonException): pass +@enum.unique +class TestResult(enum.Enum): + + OK = 'OK' + TIMEOUT = 'TIMEOUT' + SKIP = 'SKIP' + FAIL = 'FAIL' + + class TestRun: def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd, env): + assert isinstance(res, TestResult) self.res = res self.returncode = returncode self.duration = duration @@ -123,7 +136,7 @@ class TestRun: if self.cmd is None: res += 'NONE\n' else: - res += "%s%s\n" % (''.join(["%s='%s' " % (k, v) for k, v in self.env.items()]), ' ' .join(self.cmd)) + res += '%s%s\n' % (''.join(["%s='%s' " % (k, v) for k, v in self.env.items()]), ' ' .join(self.cmd)) if self.stdo: res += '--- stdout ---\n' res += self.stdo @@ -148,7 +161,7 @@ def decode(stream): def write_json_log(jsonlogfile, test_name, result): jresult = {'name': test_name, 'stdout': result.stdo, - 'result': result.res, + 'result': result.res.value, 'duration': result.duration, 'returncode': result.returncode, 'command': result.cmd} @@ -181,6 +194,139 @@ def load_tests(build_dir): obj = pickle.load(f) return obj + +class SingleTestRunner: + + def __init__(self, test, env, options): + self.test = test + self.env = env + self.options = options + + def _get_cmd(self): + if self.test.fname[0].endswith('.jar'): + return ['java', '-jar'] + self.test.fname + elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]): + return ['mono'] + self.test.fname + else: + if self.test.is_cross_built: + if self.test.exe_runner is None: + # Can not run test on cross compiled executable + # because there is no execute wrapper. + return None + else: + return [self.test.exe_runner] + self.test.fname + else: + return self.test.fname + + def run(self): + cmd = self._get_cmd() + if cmd is None: + skip_stdout = 'Not run because can not execute cross compiled binaries.' + return TestRun(res=TestResult.SKIP, returncode=GNU_SKIP_RETURNCODE, + should_fail=self.test.should_fail, duration=0.0, + stdo=skip_stdout, stde=None, cmd=None, env=self.test.env) + else: + wrap = TestHarness.get_wrapper(self.options) + if self.options.gdb: + self.test.timeout = None + return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args) + + def _run_cmd(self, cmd): + starttime = time.time() + + if len(self.test.extra_paths) > 0: + self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH'] + + # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, + # (i.e., the test or the environment don't explicitly set it), set + # it ourselves. We do this unconditionally for regular tests + # because it is extremely useful to have. + # Setting MALLOC_PERTURB_="0" will completely disable this feature. + if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark: + self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) + + stdout = None + stderr = None + if not self.options.verbose: + stdout = subprocess.PIPE + stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT + + # Let gdb handle ^C instead of us + if self.options.gdb: + previous_sigint_handler = signal.getsignal(signal.SIGINT) + # Make the meson executable ignore SIGINT while gdb is running. + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def preexec_fn(): + if self.options.gdb: + # Restore the SIGINT handler for the child process to + # ensure it can handle it. + signal.signal(signal.SIGINT, signal.SIG_DFL) + else: + # We don't want setsid() in gdb because gdb needs the + # terminal in order to handle ^C and not show tcsetpgrp() + # errors avoid not being able to use the terminal. + os.setsid() + + p = subprocess.Popen(cmd, + stdout=stdout, + stderr=stderr, + env=self.env, + cwd=self.test.workdir, + preexec_fn=preexec_fn if not is_windows() else None) + timed_out = False + kill_test = False + if self.test.timeout is None: + timeout = None + elif self.options.timeout_multiplier is not None: + timeout = self.test.timeout * self.options.timeout_multiplier + else: + timeout = self.test.timeout + try: + (stdo, stde) = p.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + if self.options.verbose: + print('%s time out (After %d seconds)' % (self.test.name, timeout)) + timed_out = True + except KeyboardInterrupt: + mlog.warning('CTRL-C detected while running %s' % (self.test.name)) + kill_test = True + finally: + if self.options.gdb: + # Let us accept ^C again + signal.signal(signal.SIGINT, previous_sigint_handler) + + if kill_test or timed_out: + # Python does not provide multiplatform support for + # killing a process and all its children so we need + # to roll our own. + if is_windows(): + subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)]) + else: + try: + os.killpg(os.getpgid(p.pid), signal.SIGKILL) + except ProcessLookupError: + # Sometimes (e.g. with Wine) this happens. + # There's nothing we can do (maybe the process + # already died) so carry on. + pass + (stdo, stde) = p.communicate() + endtime = time.time() + duration = endtime - starttime + stdo = decode(stdo) + if stde: + stde = decode(stde) + if timed_out: + res = TestResult.TIMEOUT + elif p.returncode == GNU_SKIP_RETURNCODE: + res = TestResult.SKIP + elif self.test.should_fail == bool(p.returncode): + res = TestResult.OK + else: + res = TestResult.FAIL + return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env) + + class TestHarness: def __init__(self, options): self.options = options @@ -208,7 +354,7 @@ class TestHarness: self.jsonlogfile.close() def merge_suite_options(self, options, test): - if ":" in options.setup: + if ':' in options.setup: if options.setup not in self.build_data.test_setups: sys.exit("Unknown test setup '%s'." % options.setup) current = self.build_data.test_setups[options.setup] @@ -229,7 +375,8 @@ class TestHarness: options.wrapper = current.exe_wrapper return current.env.get_env(os.environ.copy()) - def get_test_env(self, options, test): + def get_test_runner(self, test): + options = deepcopy(self.options) if options.setup: env = self.merge_suite_options(options, test) else: @@ -237,153 +384,33 @@ class TestHarness: if isinstance(test.env, build.EnvironmentVariables): test.env = test.env.get_env(env) env.update(test.env) - return env - - def run_single_test(self, test): - if test.fname[0].endswith('.jar'): - cmd = ['java', '-jar'] + test.fname - elif not test.is_cross_built and run_with_mono(test.fname[0]): - cmd = ['mono'] + test.fname - else: - if test.is_cross_built: - if test.exe_runner is None: - # Can not run test on cross compiled executable - # because there is no execute wrapper. - cmd = None - else: - cmd = [test.exe_runner] + test.fname - else: - cmd = test.fname - - if cmd is None: - res = 'SKIP' - duration = 0.0 - stdo = 'Not run because can not execute cross compiled binaries.' - stde = None - returncode = GNU_SKIP_RETURNCODE + return SingleTestRunner(test, env, options) + + def process_test_result(self, result): + if result.res is TestResult.TIMEOUT: + self.timeout_count += 1 + self.fail_count += 1 + elif result.res is TestResult.SKIP: + self.skip_count += 1 + elif result.res is TestResult.OK: + self.success_count += 1 + elif result.res is TestResult.FAIL: + self.fail_count += 1 else: - test_opts = deepcopy(self.options) - test_env = self.get_test_env(test_opts, test) - wrap = self.get_wrapper(test_opts) - - if test_opts.gdb: - test.timeout = None - - cmd = wrap + cmd + test.cmd_args + self.options.test_args - starttime = time.time() - - if len(test.extra_paths) > 0: - test_env['PATH'] = os.pathsep.join(test.extra_paths + ['']) + test_env['PATH'] - - # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, - # (i.e., the test or the environment don't explicitly set it), set - # it ourselves. We do this unconditionally for regular tests - # because it is extremely useful to have. - # Setting MALLOC_PERTURB_="0" will completely disable this feature. - if ('MALLOC_PERTURB_' not in test_env or not test_env['MALLOC_PERTURB_']) and not self.options.benchmark: - test_env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) - - stdout = None - stderr = None - if not self.options.verbose: - stdout = subprocess.PIPE - stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT - - # Let gdb handle ^C instead of us - if test_opts.gdb: - previous_sigint_handler = signal.getsignal(signal.SIGINT) - # Make the meson executable ignore SIGINT while gdb is running. - signal.signal(signal.SIGINT, signal.SIG_IGN) - - def preexec_fn(): - if test_opts.gdb: - # Restore the SIGINT handler for the child process to - # ensure it can handle it. - signal.signal(signal.SIGINT, signal.SIG_DFL) - else: - # We don't want setsid() in gdb because gdb needs the - # terminal in order to handle ^C and not show tcsetpgrp() - # errors avoid not being able to use the terminal. - os.setsid() - - p = subprocess.Popen(cmd, - stdout=stdout, - stderr=stderr, - env=test_env, - cwd=test.workdir, - preexec_fn=preexec_fn if not is_windows() else None) - timed_out = False - kill_test = False - if test.timeout is None: - timeout = None - elif test_opts.timeout_multiplier is not None: - timeout = test.timeout * test_opts.timeout_multiplier - else: - timeout = test.timeout - try: - (stdo, stde) = p.communicate(timeout=timeout) - except subprocess.TimeoutExpired: - if self.options.verbose: - print("%s time out (After %d seconds)" % (test.name, timeout)) - timed_out = True - except KeyboardInterrupt: - mlog.warning("CTRL-C detected while running %s" % (test.name)) - kill_test = True - finally: - if test_opts.gdb: - # Let us accept ^C again - signal.signal(signal.SIGINT, previous_sigint_handler) - - if kill_test or timed_out: - # Python does not provide multiplatform support for - # killing a process and all its children so we need - # to roll our own. - if is_windows(): - subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)]) - else: - try: - os.killpg(os.getpgid(p.pid), signal.SIGKILL) - except ProcessLookupError: - # Sometimes (e.g. with Wine) this happens. - # There's nothing we can do (maybe the process - # already died) so carry on. - pass - (stdo, stde) = p.communicate() - endtime = time.time() - duration = endtime - starttime - stdo = decode(stdo) - if stde: - stde = decode(stde) - if timed_out: - res = 'TIMEOUT' - self.timeout_count += 1 - self.fail_count += 1 - elif p.returncode == GNU_SKIP_RETURNCODE: - res = 'SKIP' - self.skip_count += 1 - elif test.should_fail == bool(p.returncode): - res = 'OK' - self.success_count += 1 - else: - res = 'FAIL' - self.fail_count += 1 - returncode = p.returncode - result = TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env) - - return result + sys.exit('Unknown test result encountered: {}'.format(result.res)) def print_stats(self, numlen, tests, name, result, i): startpad = ' ' * (numlen - len('%d' % (i + 1))) num = '%s%d/%d' % (startpad, i + 1, len(tests)) padding1 = ' ' * (38 - len(name)) - padding2 = ' ' * (8 - len(result.res)) + padding2 = ' ' * (8 - len(result.res.value)) result_str = '%s %s %s%s%s%5.2f s' % \ - (num, name, padding1, result.res, padding2, result.duration) - if not self.options.quiet or result.res != 'OK': - if result.res != 'OK' and mlog.colorize_console: - if result.res == 'FAIL' or result.res == 'TIMEOUT': + (num, name, padding1, result.res.value, padding2, result.duration) + if not self.options.quiet or result.res is not TestResult.OK: + if result.res is not TestResult.OK and mlog.colorize_console: + if result.res is TestResult.FAIL or result.res is TestResult.TIMEOUT: decorator = mlog.red - elif result.res == 'SKIP': + elif result.res is TestResult.SKIP: decorator = mlog.yellow else: sys.exit('Unreachable code was ... well ... reached.') @@ -449,6 +476,25 @@ TIMEOUT: %4d (prj_match, st_match) = TestHarness.split_suite_string(suite) for prjst in test.suite: (prj, st) = TestHarness.split_suite_string(prjst) + + # the SUITE can be passed as + # suite_name + # or + # project_name:suite_name + # so we need to select only the test belonging to project_name + + # this if hanlde the first case (i.e., SUITE == suite_name) + + # in this way we can run tests belonging to different + # (sub)projects which share the same suite_name + if not st_match and st == prj_match: + return True + + # these two conditions are needed to handle the second option + # i.e., SUITE == project_name:suite_name + + # in this way we select the only the tests of + # project_name with suite_name if prj_match and prj != prj_match: continue if st_match and st != st_match: @@ -515,7 +561,8 @@ TIMEOUT: %4d self.logfile.write('Log of Meson test suite run on %s\n\n' % datetime.datetime.now().isoformat()) - def get_wrapper(self, options): + @staticmethod + def get_wrapper(options): wrap = [] if options.gdb: wrap = ['gdb', '--quiet', '--nh'] @@ -556,12 +603,15 @@ TIMEOUT: %4d if not test.is_parallel or self.options.gdb: self.drain_futures(futures) futures = [] - res = self.run_single_test(test) + single_test = self.get_test_runner(test) + res = single_test.run() + self.process_test_result(res) self.print_stats(numlen, tests, visible_name, res, i) else: if not executor: executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes) - f = executor.submit(self.run_single_test, test) + single_test = self.get_test_runner(test) + f = executor.submit(single_test.run) futures.append((f, numlen, tests, visible_name, i)) if self.options.repeat > 1 and self.fail_count: break @@ -584,10 +634,11 @@ TIMEOUT: %4d result.cancel() if self.options.verbose: result.result() + self.process_test_result(result.result()) self.print_stats(numlen, tests, name, result.result(), i) def run_special(self): - 'Tests run by the user, usually something like "under gdb 1000 times".' + '''Tests run by the user, usually something like "under gdb 1000 times".''' if self.is_run: raise RuntimeError('Can not use run_special after a full run.') tests = self.get_tests() @@ -604,7 +655,7 @@ def list_tests(th): def rebuild_all(wd): if not os.path.isfile(os.path.join(wd, 'build.ninja')): - print("Only ninja backend is supported to rebuild tests before running them.") + print('Only ninja backend is supported to rebuild tests before running them.') return True ninja = environment.detect_ninja() @@ -616,13 +667,13 @@ def rebuild_all(wd): p.communicate() if p.returncode != 0: - print("Could not rebuild") + print('Could not rebuild') return False return True def run(args): - options = parser.parse_args(args) + options = buildparser().parse_args(args) if options.benchmark: options.num_processes = 1 @@ -645,7 +696,7 @@ def run(args): if check_bin is not None: exe = ExternalProgram(check_bin, silent=True) if not exe.found(): - sys.exit("Could not find requested program: %s" % check_bin) + sys.exit('Could not find requested program: %s' % check_bin) options.wd = os.path.abspath(options.wd) if not options.list and not options.no_rebuild: diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index fad7ba0..1127288 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -29,18 +29,20 @@ from mesonbuild import mlog import sys, traceback import argparse -parser = argparse.ArgumentParser(prog='meson rewrite') - -parser.add_argument('--sourcedir', default='.', - help='Path to source directory.') -parser.add_argument('--target', default=None, - help='Name of target to edit.') -parser.add_argument('--filename', default=None, - help='Name of source file to add or remove to target.') -parser.add_argument('commands', nargs='+') +def buildparser(): + parser = argparse.ArgumentParser(prog='meson rewrite') + + parser.add_argument('--sourcedir', default='.', + help='Path to source directory.') + parser.add_argument('--target', default=None, + help='Name of target to edit.') + parser.add_argument('--filename', default=None, + help='Name of source file to add or remove to target.') + parser.add_argument('commands', nargs='+') + return parser def run(args): - options = parser.parse_args(args) + options = buildparser().parse_args(args) if options.target is None or options.filename is None: sys.exit("Must specify both target and filename.") print('This tool is highly experimental, use with care.') diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index 2d1f8c3..916c84f 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -14,87 +14,135 @@ from mesonbuild import environment -import sys, os, subprocess, pathlib +import argparse, sys, os, subprocess, pathlib + +def coverage(outputs, source_root, subproject_root, build_root, log_dir): + outfiles = [] + exitcode = 0 -def coverage(source_root, build_root, log_dir): (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools() - if gcovr_exe: - # gcovr >= 3.1 interprets rootdir differently - if gcovr_new_rootdir: - rootdir = build_root - else: - rootdir = source_root - subprocess.check_call([gcovr_exe, - '-x', - '-r', rootdir, - '-o', os.path.join(log_dir, 'coverage.xml'), - ]) - subprocess.check_call([gcovr_exe, - '-r', rootdir, - '-o', os.path.join(log_dir, 'coverage.txt'), - ]) - if lcov_exe and genhtml_exe: - htmloutdir = os.path.join(log_dir, 'coveragereport') - covinfo = os.path.join(log_dir, 'coverage.info') - initial_tracefile = covinfo + '.initial' - run_tracefile = covinfo + '.run' - raw_tracefile = covinfo + '.raw' - subprocess.check_call([lcov_exe, - '--directory', build_root, - '--capture', - '--initial', - '--output-file', - initial_tracefile]) - subprocess.check_call([lcov_exe, - '--directory', build_root, - '--capture', - '--output-file', run_tracefile, - '--no-checksum', - '--rc', 'lcov_branch_coverage=1', - ]) - # Join initial and test results. - subprocess.check_call([lcov_exe, - '-a', initial_tracefile, - '-a', run_tracefile, - '-o', raw_tracefile]) - # Remove all directories outside the source_root from the covinfo - subprocess.check_call([lcov_exe, - '--extract', raw_tracefile, - os.path.join(source_root, '*'), - '--output-file', covinfo]) - subprocess.check_call([genhtml_exe, - '--prefix', build_root, - '--output-directory', htmloutdir, - '--title', 'Code coverage', - '--legend', - '--show-details', - '--branch-coverage', - covinfo]) - elif gcovr_exe and gcovr_new_rootdir: - htmloutdir = os.path.join(log_dir, 'coveragereport') - subprocess.check_call([gcovr_exe, - '--html', - '--html-details', - '-r', build_root, - '-o', os.path.join(htmloutdir, 'index.html'), - ]) - if gcovr_exe: + + # gcovr >= 3.1 interprets rootdir differently + if gcovr_new_rootdir: + gcovr_rootdir = build_root + else: + gcovr_rootdir = source_root + + if not outputs or 'xml' in outputs: + if gcovr_exe: + subprocess.check_call([gcovr_exe, + '-x', + '-r', gcovr_rootdir, + '-e', subproject_root, + '-o', os.path.join(log_dir, 'coverage.xml'), + ]) + outfiles.append(('Xml', pathlib.Path(log_dir, 'coverage.xml'))) + elif outputs: + print('gcovr needed to generate Xml coverage report') + exitcode = 1 + + if not outputs or 'text' in outputs: + if gcovr_exe: + subprocess.check_call([gcovr_exe, + '-r', gcovr_rootdir, + '-e', subproject_root, + '-o', os.path.join(log_dir, 'coverage.txt'), + ]) + outfiles.append(('Text', pathlib.Path(log_dir, 'coverage.txt'))) + elif outputs: + print('gcovr needed to generate text coverage report') + exitcode = 1 + + if not outputs or 'html' in outputs: + if lcov_exe and genhtml_exe: + htmloutdir = os.path.join(log_dir, 'coveragereport') + covinfo = os.path.join(log_dir, 'coverage.info') + initial_tracefile = covinfo + '.initial' + run_tracefile = covinfo + '.run' + raw_tracefile = covinfo + '.raw' + subprocess.check_call([lcov_exe, + '--directory', build_root, + '--capture', + '--initial', + '--output-file', + initial_tracefile]) + subprocess.check_call([lcov_exe, + '--directory', build_root, + '--capture', + '--output-file', run_tracefile, + '--no-checksum', + '--rc', 'lcov_branch_coverage=1', + ]) + # Join initial and test results. + subprocess.check_call([lcov_exe, + '-a', initial_tracefile, + '-a', run_tracefile, + '-o', raw_tracefile]) + # Remove all directories outside the source_root from the covinfo + subprocess.check_call([lcov_exe, + '--extract', raw_tracefile, + os.path.join(source_root, '*'), + '--output-file', covinfo]) + # Remove all directories inside subproject dir + subprocess.check_call([lcov_exe, + '--remove', covinfo, + os.path.join(subproject_root, '*'), + '--output-file', covinfo]) + subprocess.check_call([genhtml_exe, + '--prefix', build_root, + '--output-directory', htmloutdir, + '--title', 'Code coverage', + '--legend', + '--show-details', + '--branch-coverage', + covinfo]) + outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html'))) + elif gcovr_exe and gcovr_new_rootdir: + htmloutdir = os.path.join(log_dir, 'coveragereport') + if not os.path.isdir(htmloutdir): + os.mkdir(htmloutdir) + subprocess.check_call([gcovr_exe, + '--html', + '--html-details', + '-r', build_root, + '-e', subproject_root, + '-o', os.path.join(htmloutdir, 'index.html'), + ]) + outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html'))) + elif outputs: + print('lcov/genhtml or gcovr >= 3.1 needed to generate Html coverage report') + exitcode = 1 + + if not outputs and not outfiles: + print('Need gcovr or lcov/genhtml to generate any coverage reports') + exitcode = 1 + + if outfiles: print('') - print('XML coverage report can be found at', - pathlib.Path(log_dir, 'coverage.xml').as_uri()) - print('Text coverage report can be found at', - pathlib.Path(log_dir, 'coverage.txt').as_uri()) - if (lcov_exe and genhtml_exe) or (gcovr_exe and gcovr_new_rootdir): - print('Html coverage report can be found at', - pathlib.Path(htmloutdir, 'index.html').as_uri()) - return 0 + for (filetype, path) in outfiles: + print(filetype + ' coverage report can be found at', path.as_uri()) + + return exitcode def run(args): if not os.path.isfile('build.ninja'): print('Coverage currently only works with the Ninja backend.') return 1 - source_root, build_root, log_dir = args[:] - return coverage(source_root, build_root, log_dir) + parser = argparse.ArgumentParser(description='Generate coverage reports') + parser.add_argument('--text', dest='outputs', action='append_const', + const='text', help='generate Text report') + parser.add_argument('--xml', dest='outputs', action='append_const', + const='xml', help='generate Xml report') + parser.add_argument('--html', dest='outputs', action='append_const', + const='html', help='generate Html report') + parser.add_argument('source_root') + parser.add_argument('subproject_root') + parser.add_argument('build_root') + parser.add_argument('log_dir') + options = parser.parse_args(args) + return coverage(options.outputs, options.source_root, + options.subproject_root, options.build_root, + options.log_dir) if __name__ == '__main__': sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index ee63147..41ede1d 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -14,6 +14,7 @@ import sys, struct +import shutil, subprocess SHT_STRTAB = 3 DT_NEEDED = 1 @@ -337,20 +338,68 @@ class Elf(DataSizes): entry.write(self.bf) return None +def fix_elf(fname, new_rpath, verbose=True): + with Elf(fname, verbose) as e: + if new_rpath is None: + e.print_rpath() + e.print_runpath() + else: + e.fix_rpath(new_rpath) + +def get_darwin_rpaths_to_remove(fname): + out = subprocess.check_output(['otool', '-l', fname], universal_newlines=True) + result = [] + current_cmd = 'FOOBAR' + for line in out.split('\n'): + line = line.strip() + if ' ' not in line: + continue + key, value = line.strip().split(' ', 1) + if key == 'cmd': + current_cmd = value + if key == 'path' and current_cmd == 'LC_RPATH': + rp = value.split('(', 1)[0].strip() + result.append(rp) + return result + +def fix_darwin(fname, new_rpath): + try: + rpaths = get_darwin_rpaths_to_remove(fname) + except subprocess.CalledProcessError: + # Otool failed, which happens when invoked on a + # non-executable target. Just return. + return + try: + for rp in rpaths: + subprocess.check_call(['install_name_tool', '-delete_rpath', rp, fname]) + if new_rpath != '': + subprocess.check_call(['install_name_tool', '-add_rpath', new_rpath, fname]) + except Exception as e: + raise + sys.exit(0) + +def fix_rpath(fname, new_rpath, verbose=True): + try: + fix_elf(fname, new_rpath, verbose) + return 0 + except SystemExit as e: + if isinstance(e.code, int) and e.code == 0: + pass + else: + raise + if shutil.which('install_name_tool'): + fix_darwin(fname, new_rpath) + return 0 + def run(args): if len(args) < 1 or len(args) > 2: print('This application resets target rpath.') print('Don\'t run this unless you know what you are doing.') print('%s: <binary file> <prefix>' % sys.argv[0]) sys.exit(1) - with Elf(args[0]) as e: - if len(args) == 1: - e.print_rpath() - e.print_runpath() - else: - new_rpath = args[1] - e.fix_rpath(new_rpath) - return 0 + fname = args[0] + new_rpath = None if len(args) == 1 else args[1] + return fix_rpath(fname, new_rpath) if __name__ == '__main__': - run(sys.argv[1:]) + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py index 2a5ee8b..3fe7fb7 100644 --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -58,6 +58,8 @@ def gtkdoc_run_check(cmd, cwd, library_path=None): if out: err_msg.append(out) raise MesonException('\n'.join(err_msg)) + elif out: + print(out) def build_gtkdoc(source_root, build_root, doc_subdir, src_subdirs, main_file, module, diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index c43702e..46d501f 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -21,8 +21,10 @@ import subprocess options = None -parser = argparse.ArgumentParser() -parser.add_argument('args', nargs='+') +def buildparser(): + parser = argparse.ArgumentParser() + parser.add_argument('args', nargs='+') + return parser def is_windows(): platname = platform.system().lower() @@ -70,7 +72,7 @@ def run_exe(exe): def run(args): global options - options = parser.parse_args(args) + options = buildparser().parse_args(args) if len(options.args) != 1: print('Test runner for Meson. Do not run on your own, mmm\'kay?') print(sys.argv[0] + ' [data file]') diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 1414ace..013f2a0 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -291,12 +291,6 @@ def run_install_script(d): print('Failed to run install script {!r}'.format(name)) sys.exit(1) -def is_elf_platform(): - platname = platform.system().lower() - if platname == 'darwin' or platname == 'windows' or platname == 'cygwin': - return False - return True - def check_for_stampfile(fname): '''Some languages e.g. Rust have output files whose names are not known at configure time. @@ -372,10 +366,9 @@ def install_targets(d): print("Symlink creation does not work on this platform. " "Skipping all symlinking.") printed_symlink_error = True - if is_elf_platform() and os.path.isfile(outname): + if os.path.isfile(outname): try: - e = depfixer.Elf(outname, False) - e.fix_rpath(install_rpath) + depfixer.fix_rpath(outname, install_rpath, False) except SystemExit as e: if isinstance(e.code, int) and e.code == 0: pass diff --git a/mesonbuild/wrap/__init__.py b/mesonbuild/wrap/__init__.py index 019634c..6e2bc83 100644 --- a/mesonbuild/wrap/__init__.py +++ b/mesonbuild/wrap/__init__.py @@ -25,7 +25,12 @@ from enum import Enum # to use 'nofallback' so that any 'copylib' wraps will be # download as subprojects. # +# --wrap-mode=forcefallback will ignore external dependencies, +# even if they match the version requirements, and automatically +# use the fallback if one was provided. This is useful for example +# to make sure a project builds when using the fallbacks. +# # Note that these options do not affect subprojects that # are git submodules since those are only usable in git # repositories, and you almost always want to download them. -WrapMode = Enum('WrapMode', 'default nofallback nodownload') +WrapMode = Enum('WrapMode', 'default nofallback nodownload forcefallback') diff --git a/run_tests.py b/run_tests.py index 1cc3983..648e6ce 100755 --- a/run_tests.py +++ b/run_tests.py @@ -131,7 +131,7 @@ def get_fake_options(prefix): return opts def should_run_linux_cross_tests(): - return shutil.which('arm-linux-gnueabihf-gcc-7') and not platform.machine().lower().startswith('arm') + return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') def run_configure_inprocess(meson_command, commandlist): old_stdout = sys.stdout diff --git a/run_unittests.py b/run_unittests.py index 94e39c8..e0cd1ec 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -42,6 +42,7 @@ from mesonbuild.mesonlib import ( from mesonbuild.environment import Environment, detect_ninja from mesonbuild.mesonlib import MesonException, EnvironmentException from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram +import mesonbuild.modules.pkgconfig from run_tests import exe_suffix, get_fake_options from run_tests import get_builddir_target_args, get_backend_commands, Backend @@ -198,6 +199,12 @@ class InternalTests(unittest.TestCase): # Direct-adding the same library again still adds it l.append_direct('-lbar') self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar']) + # Direct-adding with absolute path deduplicates + l.append_direct('/libbaz.a') + self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a']) + # Adding libbaz again does nothing + l.append_direct('/libbaz.a') + self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a']) def test_string_templates_substitution(self): dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict @@ -439,14 +446,28 @@ class InternalTests(unittest.TestCase): for f in snippet_dir.glob('*'): self.assertTrue(f.is_file()) if f.suffix == '.md': - for line in f.open(): - m = re.match(hashcounter, line) - if m: - self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name) + with f.open() as snippet: + for line in snippet: + m = re.match(hashcounter, line) + if m: + self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name) else: if f.name != 'add_release_note_snippets_here': self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name) + def test_pkgconfig_module(self): + deps = mesonbuild.modules.pkgconfig.DependenciesHelper("thislib") + + class Mock: + pass + + mock = Mock() + mock.pcdep = Mock() + mock.pcdep.name = "some_name" + mock.version_reqs = [] + deps.add_pub_libs([mock]) + self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name") + class BasePlatformTests(unittest.TestCase): def setUp(self): @@ -461,6 +482,7 @@ class BasePlatformTests(unittest.TestCase): self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja')) self.meson_mainfile = os.path.join(src_root, 'meson.py') self.meson_args = ['--backend=' + self.backend.name] + self.meson_cross_file = None self.meson_command = meson_command + self.meson_args self.mconf_command = meson_command + ['configure'] self.mintro_command = meson_command + ['introspect'] @@ -523,16 +545,18 @@ class BasePlatformTests(unittest.TestCase): Run a command while printing the stdout and stderr to stdout, and also return a copy of it ''' - p = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=os.environ.copy(), - universal_newlines=True, cwd=workdir) - output = p.communicate()[0] - print(output) + # If this call hangs CI will just abort. It is very hard to distinguish + # between CI issue and test bug in that case. Set timeout and fail loud + # instead. + p = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=os.environ.copy(), + universal_newlines=True, cwd=workdir, timeout=60 * 5) + print(p.stdout) if p.returncode != 0: - if 'MESON_SKIP_TEST' in output: + if 'MESON_SKIP_TEST' in p.stdout: raise unittest.SkipTest('Project requested skipping.') raise subprocess.CalledProcessError(p.returncode, command) - return output + return p.stdout def init(self, srcdir, extra_args=None, default_args=True, inprocess=False): self.assertPathExists(srcdir) @@ -544,6 +568,8 @@ class BasePlatformTests(unittest.TestCase): if default_args: args += ['--prefix', self.prefix, '--libdir', self.libdir] + if self.meson_cross_file: + args += ['--cross-file', self.meson_cross_file] self.privatedir = os.path.join(self.builddir, 'meson-private') if inprocess: try: @@ -947,6 +973,12 @@ class AllPlatformTests(BasePlatformTests): self.uninstall() self.assertPathDoesNotExist(exename) + def test_forcefallback(self): + testdir = os.path.join(self.unit_test_dir, '27 forcefallback') + self.init(testdir, ['--wrap-mode=forcefallback']) + self.build() + self.run_tests() + def test_testsetups(self): if not shutil.which('valgrind'): raise unittest.SkipTest('Valgrind not installed.') @@ -978,6 +1010,7 @@ class AllPlatformTests(BasePlatformTests): self._run(self.mtest_command + ['--setup=timeout']) def test_testsetup_selection(self): + return testdir = os.path.join(self.unit_test_dir, '13 testsetup selection') self.init(testdir) self.build() @@ -1085,7 +1118,7 @@ class AllPlatformTests(BasePlatformTests): incs = [a for a in shlex.split(execmd) if a.startswith("-I")] self.assertEqual(len(incs), 9) # target private dir - self.assertPathEqual(incs[0], "-Isub4/someexe@exe") + self.assertPathEqual(incs[0], "-Isub4/sub4@@someexe@exe") # target build subdir self.assertPathEqual(incs[1], "-Isub4") # target source subdir @@ -1317,7 +1350,8 @@ class AllPlatformTests(BasePlatformTests): # \n is never substituted by the GNU pre-processor via a -D define # ' and " confuse shlex.split() even when they are escaped # % and # confuse the MSVC preprocessor - value = 'spaces and fun!@$^&*()-=_+{}[]:;<>?,./~`' + # !, ^, *, and < confuse lcc preprocessor + value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`' os.environ['CPPFLAGS'] = '-D{}="{}"'.format(define, value) os.environ['CFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) self.init(testdir, ['-D{}={}'.format(define, value)]) @@ -1811,7 +1845,8 @@ int main(int argc, char **argv) { self._run(ninja, workdir=os.path.join(tmpdir, 'builddir')) with tempfile.TemporaryDirectory() as tmpdir: - open(os.path.join(tmpdir, 'foo.' + lang), 'w').write('int main() {}') + with open(os.path.join(tmpdir, 'foo.' + lang), 'w') as f: + f.write('int main() {}') self._run(meson_command + ['init', '-b'], workdir=tmpdir) # The test uses mocking and thus requires that @@ -1883,6 +1918,15 @@ int main(int argc, char **argv) { self.init(testdir, extra_args=['--layout=flat']) self.build() + def test_identical_target_name_in_subdir_flat_layout(self): + ''' + Test that identical targets in different subdirs do not collide + if layout is flat. + ''' + testdir = os.path.join(self.common_test_dir, '192 same target name flat layout') + self.init(testdir, extra_args=['--layout=flat']) + self.build() + def test_flock(self): exception_raised = False with tempfile.TemporaryDirectory() as tdir: @@ -1895,6 +1939,16 @@ int main(int argc, char **argv) { exception_raised = True self.assertTrue(exception_raised, 'Double locking did not raise exception.') + def test_check_module_linking(self): + """ + Test that shared modules are not linked with targets(link_with:) #2865 + """ + tdir = os.path.join(self.unit_test_dir, '26 shared_mod linking') + out = self.init(tdir) + msg = ('''WARNING: target links against shared modules. This is not +recommended as it can lead to undefined behaviour on some platforms''') + self.assertIn(msg, out) + def test_ndebug_if_release_disabled(self): testdir = os.path.join(self.unit_test_dir, '25 ndebug if-release') self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release']) @@ -1909,6 +1963,55 @@ int main(int argc, char **argv) { exe = os.path.join(self.builddir, 'main') self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip()) + def test_guessed_linker_dependencies(self): + ''' + Test that meson adds dependencies for libraries based on the final + linker command line. + ''' + # build library + testdirbase = os.path.join(self.unit_test_dir, '26 guessed linker dependencies') + testdirlib = os.path.join(testdirbase, 'lib') + extra_args = None + env = Environment(testdirlib, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) + if env.detect_c_compiler(False).get_id() != 'msvc': + # static libraries are not linkable with -l with msvc because meson installs them + # as .a files which unix_args_to_native will not know as it expects libraries to use + # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc + # this tests needs to use shared libraries to test the path resolving logic in the + # dependency generation code path. + extra_args = ['--default-library', 'static'] + self.init(testdirlib, extra_args=extra_args) + self.build() + self.install() + libbuilddir = self.builddir + installdir = self.installdir + libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib') + + # build user of library + self.new_builddir() + # replace is needed because meson mangles platform pathes passed via LDFLAGS + os.environ["LDFLAGS"] = '-L{}'.format(libdir.replace('\\', '/')) + self.init(os.path.join(testdirbase, 'exe')) + del os.environ["LDFLAGS"] + self.build() + self.assertBuildIsNoop() + + # rebuild library + exebuilddir = self.builddir + self.installdir = installdir + self.builddir = libbuilddir + # Microsoft's compiler is quite smart about touching import libs on changes, + # so ensure that there is actually a change in symbols. + self.setconf('-Dmore_exports=true') + self.build() + self.install() + # no ensure_backend_detects_changes needed because self.setconf did that already + + # assert user of library will be rebuild + self.builddir = exebuilddir + self.assertRebuiltTarget('app') + class FailureTests(BasePlatformTests): ''' @@ -2266,11 +2369,11 @@ class LinuxlikeTests(BasePlatformTests): os.environ['PKG_CONFIG_LIBDIR'] = os.pathsep.join([privatedir1, privatedir2]) cmd = ['pkg-config', 'dependency-test'] - out = self._run(cmd + ['--print-requires']).strip().split() + out = self._run(cmd + ['--print-requires']).strip().split('\n') self.assertEqual(sorted(out), sorted(['libexposed'])) - out = self._run(cmd + ['--print-requires-private']).strip().split() - self.assertEqual(sorted(out), sorted(['libfoo'])) + out = self._run(cmd + ['--print-requires-private']).strip().split('\n') + self.assertEqual(sorted(out), sorted(['libfoo >= 1.0'])) out = self._run(cmd + ['--cflags-only-other']).strip().split() self.assertEqual(sorted(out), sorted(['-pthread', '-DCUSTOM'])) @@ -2286,17 +2389,18 @@ class LinuxlikeTests(BasePlatformTests): '-lfoo'])) cmd = ['pkg-config', 'requires-test'] - out = self._run(cmd + ['--print-requires']).strip().split() - self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo', 'libhello'])) + out = self._run(cmd + ['--print-requires']).strip().split('\n') + self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) cmd = ['pkg-config', 'requires-private-test'] - out = self._run(cmd + ['--print-requires-private']).strip().split() - self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo', 'libhello'])) + out = self._run(cmd + ['--print-requires-private']).strip().split('\n') + self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) def test_pkg_unfound(self): testdir = os.path.join(self.unit_test_dir, '22 unfound pkgconfig') self.init(testdir) - pcfile = open(os.path.join(self.privatedir, 'somename.pc')).read() + with open(os.path.join(self.privatedir, 'somename.pc')) as f: + pcfile = f.read() self.assertFalse('blub_blob_blib' in pcfile) def test_vala_c_warnings(self): @@ -2507,8 +2611,8 @@ class LinuxlikeTests(BasePlatformTests): def test_unity_subproj(self): testdir = os.path.join(self.common_test_dir, '49 subproject') self.init(testdir, extra_args='--unity=subprojects') - self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/sublib@@simpletest@exe/simpletest-unity.c')) - self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/sublib@@sublib@sha/sublib-unity.c')) + self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/subprojects@sublib@@simpletest@exe/simpletest-unity.c')) + self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib/subprojects@sublib@@sublib@sha/sublib-unity.c')) self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c')) self.build() @@ -2723,11 +2827,12 @@ cpu = 'armv7' # Not sure if correct. endian = 'little' ''' % os.path.join(testdir, 'some_cross_tool.py')) crossfile.flush() - self.init(testdir, ['--cross-file=' + crossfile.name]) + self.meson_cross_file = crossfile.name + self.init(testdir) def test_reconfigure(self): testdir = os.path.join(self.unit_test_dir, '13 reconfigure') - self.init(testdir, ['-Db_lto=true'], default_args=False) + self.init(testdir, ['-Db_coverage=true'], default_args=False) self.build('reconfigure') def test_vala_generated_source_buildir_inside_source_tree(self): @@ -2800,6 +2905,27 @@ endian = 'little' self.assertTrue(os.path.isfile(test_exe)) subprocess.check_call(test_exe, env=myenv) + @unittest.skipIf(shutil.which('pkg-config') is None, 'Pkg-config not found.') + def test_pkgconfig_internal_libraries(self): + ''' + ''' + with tempfile.TemporaryDirectory() as tempdirname: + # build library + testdirbase = os.path.join(self.unit_test_dir, '28 pkgconfig use libraries') + testdirlib = os.path.join(testdirbase, 'lib') + self.init(testdirlib, extra_args=['--prefix=' + tempdirname, + '--libdir=lib', + '--default-library=static'], default_args=False) + self.build() + self.install(use_destdir=False) + + # build user of library + pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') + os.environ['PKG_CONFIG_PATH'] = pkg_dir + self.new_builddir() + self.init(os.path.join(testdirbase, 'app')) + self.build() + class LinuxArmCrossCompileTests(BasePlatformTests): ''' @@ -2808,7 +2934,7 @@ class LinuxArmCrossCompileTests(BasePlatformTests): def setUp(self): super().setUp() src_root = os.path.dirname(__file__) - self.meson_command += ['--cross=' + os.path.join(src_root, 'cross', 'ubuntu-armhf.txt')] + self.meson_cross_file = os.path.join(src_root, 'cross', 'ubuntu-armhf.txt') def test_cflags_cross_environment_pollution(self): ''' @@ -2822,6 +2948,68 @@ class LinuxArmCrossCompileTests(BasePlatformTests): compdb = self.get_compdb() self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command']) + def test_cross_file_overrides_always_args(self): + ''' + Test that $lang_args in cross files always override get_always_args(). + Needed for overriding the default -D_FILE_OFFSET_BITS=64 on some + architectures such as some Android versions and Raspbian. + https://github.com/mesonbuild/meson/issues/3049 + https://github.com/mesonbuild/meson/issues/3089 + ''' + testdir = os.path.join(self.unit_test_dir, '29 cross file overrides always args') + self.meson_cross_file = os.path.join(self.unit_test_dir, 'ubuntu-armhf-overrides.txt') + self.init(testdir) + compdb = self.get_compdb() + self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS') + self.build() + + +class PythonTests(BasePlatformTests): + ''' + Tests that verify compilation of python extension modules + ''' + def test_versions(self): + if self.backend is not Backend.ninja: + raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name)) + + testdir = os.path.join(self.src_root, 'test cases', 'python', '1 extmodule') + + # No python version specified, this will use meson's python + self.init(testdir) + self.build() + self.run_tests() + self.wipe() + + # When specifying a known name, (python2 / python3) the module + # will also try 'python' as a fallback and use it if the major + # version matches + try: + self.init(testdir, ['-Dpython=python2']) + self.build() + self.run_tests() + except unittest.SkipTest: + # python2 is not necessarily installed on the test machine, + # if it is not, or the python headers can't be found, the test + # will raise MESON_SKIP_TEST, we could check beforehand what version + # of python is available, but it's a bit of a chicken and egg situation, + # as that is the job of the module, so we just ask for forgiveness rather + # than permission. + pass + + self.wipe() + + # The test is configured to error out with MESON_SKIP_TEST + # in case it could not find python + with self.assertRaises(unittest.SkipTest): + self.init(testdir, ['-Dpython=not-python']) + self.wipe() + + # While dir is an external command on both Windows and Linux, + # it certainly isn't python + with self.assertRaises(unittest.SkipTest): + self.init(testdir, ['-Dpython=dir']) + self.wipe() + class RewriterTests(unittest.TestCase): @@ -2897,7 +3085,7 @@ def unset_envs(): if __name__ == '__main__': unset_envs() - cases = ['InternalTests', 'AllPlatformTests', 'FailureTests'] + cases = ['InternalTests', 'AllPlatformTests', 'FailureTests', 'PythonTests'] if not is_windows(): cases += ['LinuxlikeTests'] if should_run_linux_cross_tests(): diff --git a/skip_ci.py b/skip_ci.py new file mode 100755 index 0000000..752dfdc --- /dev/null +++ b/skip_ci.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# Copyright 2018 The Meson development team + +# 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. + +from __future__ import print_function + +import argparse +import os +import subprocess +import sys +import traceback + + +def check_pr(is_pr_env): + if is_pr_env not in os.environ: + print('This is not pull request: {} is not set'.format(is_pr_env)) + sys.exit() + elif os.environ[is_pr_env] == 'false': + print('This is not pull request: {} is false'.format(is_pr_env)) + sys.exit() + + +def get_base_branch(base_env): + if base_env not in os.environ: + print('Unable to determine base branch: {} is not set'.format(base_env)) + sys.exit() + return os.environ[base_env] + + +def get_git_files(base): + diff = subprocess.check_output(['git', 'diff', '--name-only', base + '...HEAD']) + return diff.strip().split(b'\n') + + +def is_documentation(filename): + return filename.startswith(b'docs/') + + +def main(): + try: + parser = argparse.ArgumentParser(description='CI Skipper') + parser.add_argument('--base-branch-env', required=True, + help='Branch push is targeted to') + parser.add_argument('--is-pull-env', required=True, + help='Variable set if it is a PR') + args = parser.parse_args() + check_pr(args.is_pull_env) + base = get_base_branch(args.base_branch_env) + if all(is_documentation(f) for f in get_git_files(base)): + print("Don't run CI for documentation-only changes, add '[skip ci]' to commit title.") + print('See http://mesonbuild.com/Contributing.html#skipping-integration-tests') + sys.exit(1) + except Exception: + # If this script fails we want build to proceed. + # Failure likely means some corner case we did not consider or bug. + # Either case this should not prevent CI from running if it is needed, + # and we tolerate it if it is run where it is not required. + traceback.print_exc() + print('There is a BUG in skip_ci.py, exiting.') + sys.exit() + +if __name__ == '__main__': + main() diff --git a/test cases/common/112 has arg/meson.build b/test cases/common/112 has arg/meson.build index 27290a1..ba07311 100644 --- a/test cases/common/112 has arg/meson.build +++ b/test cases/common/112 has arg/meson.build @@ -39,11 +39,17 @@ assert(l2.length() == 0, 'First supported did not return empty array.') if cc.get_id() == 'gcc' pre_arg = '-Wformat' - anti_pre_arg = '-Wno-format' + # NOTE: We have special handling for -Wno-foo args because gcc silently + # ignores unknown -Wno-foo args unless you pass -Werror, so for this test, we + # pass it as two separate arguments. + anti_pre_arg = ['-W', 'no-format'] arg = '-Werror=format-security' assert(not cc.has_multi_arguments([anti_pre_arg, arg]), 'Arg that should be broken is not.') 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.') + # Test that gcc correctly errors out on unknown -Wno flags + assert(not cc.has_argument('-Wno-lol-meson-test-flags'), 'should error out on unknown -Wno args') + assert(not cc.has_multi_arguments(['-Wno-pragmas', '-Wno-lol-meson-test-flags']), 'should error out even if some -Wno args are valid') endif if cc.get_id() == 'clang' and cc.version().version_compare('<=4.0.0') diff --git a/test cases/common/13 pch/meson.build b/test cases/common/13 pch/meson.build index 9ed6512..e144aa5 100644 --- a/test cases/common/13 pch/meson.build +++ b/test cases/common/13 pch/meson.build @@ -1,4 +1,10 @@ project('pch test', 'c') +cc = meson.get_compiler('c') +cc_id = cc.get_id() +if cc_id == 'lcc' + error('MESON_SKIP_TEST: Elbrus compiler does not support PCH.') +endif + exe = executable('prog', 'prog.c', c_pch : ['pch/prog_pch.c', 'pch/prog.h']) diff --git a/test cases/common/132 dependency file generation/meson.build b/test cases/common/132 dependency file generation/meson.build index dcfdcd9..cd66cb7 100644 --- a/test cases/common/132 dependency file generation/meson.build +++ b/test cases/common/132 dependency file generation/meson.build @@ -1,11 +1,13 @@ project('dep file gen', 'c') -cc_id = meson.get_compiler('c').get_id() -if cc_id == 'intel' - # ICC does not escape spaces in paths in the dependency file, so Ninja +cc_id = meson.get_compiler('c').get_id() +cc_ver = meson.get_compiler('c').version() + +if cc_id == 'intel' or (cc_id == 'lcc' and cc_ver.version_compare('<=1.23.08') + # ICC and LCC <= 1.23.08 do not escape spaces in paths in the dependency file, so Ninja # (correctly) thinks that the rule has multiple outputs and errors out: # 'depfile has multiple output paths' - error('MESON_SKIP_TEST: Skipping test with Intel compiler because it generates broken dependency files') + error('MESON_SKIP_TEST: Skipping test because your compiler is known to generate broken dependency files') endif e = executable('main file', 'main .c') diff --git a/test cases/common/138 include order/inc1/hdr.h b/test cases/common/138 include order/inc1/hdr.h new file mode 100644 index 0000000..9d755a8 --- /dev/null +++ b/test cases/common/138 include order/inc1/hdr.h @@ -0,0 +1 @@ +#define SOME_DEFINE 42 diff --git a/test cases/common/138 include order/inc2/hdr.h b/test cases/common/138 include order/inc2/hdr.h new file mode 100644 index 0000000..2ebcaca --- /dev/null +++ b/test cases/common/138 include order/inc2/hdr.h @@ -0,0 +1 @@ +#undef SOME_DEFINE diff --git a/test cases/common/138 include order/meson.build b/test cases/common/138 include order/meson.build index c370bb1..9f275b8 100644 --- a/test cases/common/138 include order/meson.build +++ b/test cases/common/138 include order/meson.build @@ -30,3 +30,7 @@ f = executable('somefxe', 'sub4/main.c', test('eh', e) test('oh', f) + +# Test that the order in include_directories() is maintained +incs = include_directories('inc1', 'inc2') +executable('ordertest', 'ordertest.c', include_directories: incs) diff --git a/test cases/common/138 include order/ordertest.c b/test cases/common/138 include order/ordertest.c new file mode 100644 index 0000000..0d9173f --- /dev/null +++ b/test cases/common/138 include order/ordertest.c @@ -0,0 +1,11 @@ +#include "hdr.h" + +#if !defined(SOME_DEFINE) || SOME_DEFINE != 42 +#error "Should have picked up hdr.h from inc1/hdr.h" +#endif + +int +main (int c, char ** argv) +{ + return 0; +} diff --git a/test cases/common/142 compute int/config.h.in b/test cases/common/142 compute int/config.h.in index ad8d077..0de63ab 100644 --- a/test cases/common/142 compute int/config.h.in +++ b/test cases/common/142 compute int/config.h.in @@ -1,2 +1,4 @@ #define INTSIZE @INTSIZE@ #define FOOBAR_IN_CONFIG_H @FOOBAR@ +#define MAXINT @MAXINT@ +#define MININT @MININT@ diff --git a/test cases/common/142 compute int/meson.build b/test cases/common/142 compute int/meson.build index 43553fe..22bd266 100644 --- a/test cases/common/142 compute int/meson.build +++ b/test cases/common/142 compute int/meson.build @@ -7,11 +7,15 @@ cc = meson.get_compiler('c') intsize = cc.compute_int('sizeof(int)', low : 1, high : 16, guess : 4) foobar = cc.compute_int('FOOBAR_IN_FOOBAR_H', prefix : '#include "foobar.h"', include_directories : inc) +maxint = cc.compute_int('INT_MAX', prefix: '#include <limits.h>') +minint = cc.compute_int('INT_MIN', prefix: '#include <limits.h>') cd = configuration_data() cd.set('INTSIZE', intsize) cd.set('FOOBAR', foobar) cd.set('CONFIG', 'config.h') +cd.set('MAXINT', maxint) +cd.set('MININT', minint) configure_file(input : 'config.h.in', output : 'config.h', configuration : cd) s = configure_file(input : 'prog.c.in', output : 'prog.c', configuration : cd) @@ -23,11 +27,15 @@ cpp = meson.get_compiler('cpp') intsize = cpp.compute_int('sizeof(int)') foobar = cpp.compute_int('FOOBAR_IN_FOOBAR_H', prefix : '#include "foobar.h"', include_directories : inc) +maxint = cpp.compute_int('INT_MAX', prefix: '#include <limits.h>') +minint = cpp.compute_int('INT_MIN', prefix: '#include <limits.h>') cdpp = configuration_data() cdpp.set('INTSIZE', intsize) cdpp.set('FOOBAR', foobar) cdpp.set('CONFIG', 'config.hpp') +cdpp.set('MAXINT', maxint) +cdpp.set('MININT', minint) configure_file(input : 'config.h.in', output : 'config.hpp', configuration : cdpp) spp = configure_file(input : 'prog.c.in', output : 'prog.cc', configuration : cdpp) diff --git a/test cases/common/142 compute int/prog.c.in b/test cases/common/142 compute int/prog.c.in index 3ff1463..ff1ad55 100644 --- a/test cases/common/142 compute int/prog.c.in +++ b/test cases/common/142 compute int/prog.c.in @@ -1,6 +1,7 @@ #include "@CONFIG@" #include <stdio.h> #include <wchar.h> +#include <limits.h> #include "foobar.h" int main(int argc, char **argv) { @@ -12,5 +13,13 @@ int main(int argc, char **argv) { fprintf(stderr, "Mismatch: computed int %d, should be %d.\n", FOOBAR_IN_CONFIG_H, FOOBAR_IN_FOOBAR_H); return 1; } + if(MAXINT != INT_MAX) { + fprintf(stderr, "Mismatch: computed max int %d, should be %d.\n", MAXINT, INT_MAX); + return 1; + } + if(MININT != INT_MIN) { + fprintf(stderr, "Mismatch: computed min int %d, should be %d.\n", MININT, INT_MIN); + return 1; + } return 0; } diff --git a/test cases/common/16 configure file/config7.h.in b/test cases/common/16 configure file/config7.h.in new file mode 100644 index 0000000..edd0bb3 --- /dev/null +++ b/test cases/common/16 configure file/config7.h.in @@ -0,0 +1,16 @@ +/* No escape */ +#define MESSAGE1 "${var1}" + +/* Single escape means no replace */ +#define MESSAGE2 "\${var1}" + +/* Replace pairs of escapes before '@' or '\@' with escape characters + * (note we have to double number of pairs due to C string escaping) + */ +#define MESSAGE3 "\\\\${var1}" + +/* Pairs of escapes and then single escape to avoid replace */ +#define MESSAGE4 "\\\\\${var1}" + +/* Check escape character outside variables */ +#define MESSAGE5 "\\ ${ \${ \\\\${ \\\\\${" diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index 71a2563..5c3a1a5 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -137,3 +137,15 @@ cfile = configure_file(input : 'config.h.in', output : 'do_not_get_installed.h', install_dir : '', configuration : conf) + +# Test escaping with cmake format +conf7 = configuration_data() +conf7.set('var1', 'foo') +conf7.set('var2', 'bar') +configure_file( + input : 'config7.h.in', + output : '@BASENAME@', + format : 'cmake', + configuration : conf7 +) +test('test7', executable('prog7', 'prog7.c')) diff --git a/test cases/common/16 configure file/prog7.c b/test cases/common/16 configure file/prog7.c new file mode 100644 index 0000000..0bb7d13 --- /dev/null +++ b/test cases/common/16 configure file/prog7.c @@ -0,0 +1,10 @@ +#include <string.h> +#include <config7.h> + +int main(int argc, char **argv) { + return strcmp(MESSAGE1, "foo") + || strcmp(MESSAGE2, "${var1}") + || strcmp(MESSAGE3, "\\foo") + || strcmp(MESSAGE4, "\\${var1}") + || strcmp(MESSAGE5, "\\ ${ ${ \\${ \\${"); +} diff --git a/test cases/common/168 disabler/meson.build b/test cases/common/168 disabler/meson.build index 7ca82b7..1956cd3 100644 --- a/test cases/common/168 disabler/meson.build +++ b/test cases/common/168 disabler/meson.build @@ -21,7 +21,7 @@ else number = 2 endif -assert(d == 0, 'Plain if handled incorrectly, value should be 0 but is @0@'.format(number)) +assert(number == 0, 'Plain if handled incorrectly, value should be 0 but is @0@'.format(number)) if d.found() number = 1 @@ -29,6 +29,6 @@ else number = 2 endif -assert(d == 1, 'If found handled incorrectly, value should be 1 but is @0@'.format(number)) +assert(number == 2, 'If found handled incorrectly, value should be 2 but is @0@'.format(number)) diff --git a/test cases/failing/17 same name/file.c b/test cases/common/183 same target name/file.c index 6f1c172..6f1c172 100644 --- a/test cases/failing/17 same name/file.c +++ b/test cases/common/183 same target name/file.c diff --git a/test cases/failing/17 same name/meson.build b/test cases/common/183 same target name/meson.build index 4e585d5..4e585d5 100644 --- a/test cases/failing/17 same name/meson.build +++ b/test cases/common/183 same target name/meson.build diff --git a/test cases/failing/17 same name/sub/file2.c b/test cases/common/183 same target name/sub/file2.c index a5e453d..a5e453d 100644 --- a/test cases/failing/17 same name/sub/file2.c +++ b/test cases/common/183 same target name/sub/file2.c diff --git a/test cases/failing/17 same name/sub/meson.build b/test cases/common/183 same target name/sub/meson.build index 610a4a3..610a4a3 100644 --- a/test cases/failing/17 same name/sub/meson.build +++ b/test cases/common/183 same target name/sub/meson.build diff --git a/test cases/common/187 subproject version/meson.build b/test cases/common/187 subproject version/meson.build new file mode 100644 index 0000000..bd8fc03 --- /dev/null +++ b/test cases/common/187 subproject version/meson.build @@ -0,0 +1,10 @@ +project('subproject version', 'c', + version : '2.3.4', + license: 'mylicense') + +subproject('a') + +liba_dep = dependency('a', + fallback: ['a', 'liba_dep'], + version: ['>= 0.30.0', '!= 0.99.0']) + diff --git a/test cases/common/187 subproject version/subprojects/a/meson.build b/test cases/common/187 subproject version/subprojects/a/meson.build new file mode 100644 index 0000000..dae3130 --- /dev/null +++ b/test cases/common/187 subproject version/subprojects/a/meson.build @@ -0,0 +1,5 @@ +project('mysubproject', 'c', + version : '1.0.0', + license : 'sublicense') + +liba_dep = declare_dependency (version : '1.0.0') diff --git a/test cases/common/188 subdir_done/meson.build b/test cases/common/188 subdir_done/meson.build new file mode 100644 index 0000000..5692f3a --- /dev/null +++ b/test cases/common/188 subdir_done/meson.build @@ -0,0 +1,10 @@ +# Should run, even though main.cpp does not exist and we call error in the last line. +# subdir_done jumps to end, so both lines are not executed. + +project('example exit', 'cpp') + +subdir_done() + +executable('main', 'main.cpp') +error('Unreachable') + diff --git a/test cases/common/189 bothlibraries/libfile.c b/test cases/common/189 bothlibraries/libfile.c new file mode 100644 index 0000000..085ef3b --- /dev/null +++ b/test cases/common/189 bothlibraries/libfile.c @@ -0,0 +1,7 @@ +#include "mylib.h" + +DO_EXPORT int retval = 42; + +DO_EXPORT int func() { + return retval; +} diff --git a/test cases/common/189 bothlibraries/main.c b/test cases/common/189 bothlibraries/main.c new file mode 100644 index 0000000..03a8e02 --- /dev/null +++ b/test cases/common/189 bothlibraries/main.c @@ -0,0 +1,8 @@ +#include "mylib.h" + +DO_IMPORT int func(); +DO_IMPORT int retval; + +int main(int argc, char **arg) { + return func() == retval ? 0 : 1; +} diff --git a/test cases/common/189 bothlibraries/meson.build b/test cases/common/189 bothlibraries/meson.build new file mode 100644 index 0000000..3a13d62 --- /dev/null +++ b/test cases/common/189 bothlibraries/meson.build @@ -0,0 +1,12 @@ +project('both libraries linking test', 'c') + +both_libs = both_libraries('mylib', 'libfile.c') +exe_shared = executable('prog-shared', 'main.c', link_with : both_libs.get_shared_lib()) +exe_static = executable('prog-static', 'main.c', + c_args : ['-DSTATIC_COMPILATION'], + link_with : both_libs.get_static_lib()) +exe_both = executable('prog-both', 'main.c', link_with : both_libs) + +test('runtest-shared', exe_shared) +test('runtest-static', exe_static) +test('runtest-both', exe_both) diff --git a/test cases/common/189 bothlibraries/mylib.h b/test cases/common/189 bothlibraries/mylib.h new file mode 100644 index 0000000..1038a01 --- /dev/null +++ b/test cases/common/189 bothlibraries/mylib.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef _WIN32 + #ifdef STATIC_COMPILATION + #define DO_IMPORT extern + #else + #define DO_IMPORT __declspec(dllimport) + #endif + #define DO_EXPORT __declspec(dllexport) +#else + #define DO_IMPORT extern + #define DO_EXPORT +#endif diff --git a/test cases/common/190 escape and unicode/file.c.in b/test cases/common/190 escape and unicode/file.c.in new file mode 100644 index 0000000..413ed42 --- /dev/null +++ b/test cases/common/190 escape and unicode/file.c.in @@ -0,0 +1,5 @@ +#include<stdio.h> +const char* does_it_work() { + printf("{NAME}\n"); + return "yes it does"; +} diff --git a/test cases/common/190 escape and unicode/file.py b/test cases/common/190 escape and unicode/file.py new file mode 100644 index 0000000..af67a09 --- /dev/null +++ b/test cases/common/190 escape and unicode/file.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +import sys +import os + +with open(sys.argv[1]) as fh: + content = fh.read().replace("{NAME}", sys.argv[2]) + +with open(os.path.join(sys.argv[3]), 'w') as fh: + fh.write(content) diff --git a/test cases/common/190 escape and unicode/find.py b/test cases/common/190 escape and unicode/find.py new file mode 100644 index 0000000..34a3eb8 --- /dev/null +++ b/test cases/common/190 escape and unicode/find.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +import os +import sys + +for fh in os.listdir('.'): + if os.path.isfile(fh): + if fh.endswith('.c'): + sys.stdout.write(fh + '\0') diff --git a/test cases/common/190 escape and unicode/fun.c b/test cases/common/190 escape and unicode/fun.c new file mode 100644 index 0000000..8eeb8ea --- /dev/null +++ b/test cases/common/190 escape and unicode/fun.c @@ -0,0 +1,3 @@ +int a_fun() { + return 1; +} diff --git a/test cases/common/190 escape and unicode/main.c b/test cases/common/190 escape and unicode/main.c new file mode 100644 index 0000000..0bcde16 --- /dev/null +++ b/test cases/common/190 escape and unicode/main.c @@ -0,0 +1,12 @@ +#include <string.h> + +const char* does_it_work(); + +int a_fun(); + +int main() { + if(strcmp(does_it_work(), "yes it does") != 0) { + return -a_fun(); + } + return 0; +} diff --git a/test cases/common/190 escape and unicode/meson.build b/test cases/common/190 escape and unicode/meson.build new file mode 100644 index 0000000..65377b6 --- /dev/null +++ b/test cases/common/190 escape and unicode/meson.build @@ -0,0 +1,24 @@ +project('180 escape', 'c') + +gen = generator(find_program('file.py'), arguments:['@INPUT@', 'erd\u0151', '@OUTPUT@'], output: '@BASENAME@') + +gen_file = gen.process('file.c.in') + +find_file_list = run_command(find_program('find.py')) +assert(find_file_list.returncode() == 0, 'Didn\'t find any files.') + +# Strings should support both octal \ooo and hex \xhh encodings + +found_files_oct = [] +foreach l : find_file_list.stdout().strip('\0').split('\000') + found_files_oct += [files(l)] +endforeach + +test('first', executable('first', found_files_oct + [gen_file])) + +found_files_hex = [] +foreach l : find_file_list.stdout().strip('\x00').split('\x00') + found_files_hex += [files(l)] +endforeach + +test('second', executable('second', found_files_hex + [gen_file])) diff --git a/test cases/common/191 has link arg/meson.build b/test cases/common/191 has link arg/meson.build new file mode 100644 index 0000000..255ff45 --- /dev/null +++ b/test cases/common/191 has link arg/meson.build @@ -0,0 +1,42 @@ +project('has link arg', 'c', 'cpp') + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +if cc.get_id() == 'msvc' + is_arg = '/OPT:REF' + useless = '/DEBUG' + isnt_arg = '/iambroken' +else + is_arg = '-Wl,-Lfoo' + useless = '-Wl,-Lbar' + isnt_arg = '-Wl,-iambroken' +endif + +assert(cc.has_link_argument(is_arg), 'Arg that should have worked does not work.') +assert(not cc.has_link_argument(isnt_arg), 'Arg that should be broken is not.') + +assert(cpp.has_link_argument(is_arg), 'Arg that should have worked does not work.') +assert(not cpp.has_link_argument(isnt_arg), 'Arg that should be broken is not.') + +assert(cc.get_supported_link_arguments([is_arg, isnt_arg, useless]) == [is_arg, useless], 'Arg filtering returned different result.') +assert(cpp.get_supported_link_arguments([is_arg, isnt_arg, useless]) == [is_arg, useless], 'Arg filtering returned different result.') + +# Have useless at the end to ensure that the search goes from front to back. +l1 = cc.first_supported_link_argument([isnt_arg, is_arg, isnt_arg, useless]) +l2 = cc.first_supported_link_argument(isnt_arg, isnt_arg, isnt_arg) + +assert(l1.length() == 1, 'First supported returned wrong result.') +assert(l1.get(0) == is_arg, 'First supported returned wrong argument.') +assert(l2.length() == 0, 'First supported did not return empty array.') + +l1 = cpp.first_supported_link_argument([isnt_arg, is_arg, isnt_arg, useless]) +l2 = cpp.first_supported_link_argument(isnt_arg, isnt_arg, isnt_arg) + +assert(l1.length() == 1, 'First supported returned wrong result.') +assert(l1.get(0) == is_arg, 'First supported returned wrong argument.') +assert(l2.length() == 0, 'First supported did not return empty array.') + +assert(not cc.has_multi_link_arguments([isnt_arg, is_arg]), 'Arg that should be broken is not.') +assert(cc.has_multi_link_arguments(is_arg), 'Arg that should have worked does not work.') +assert(cc.has_multi_link_arguments([useless, is_arg]), 'Arg that should have worked does not work.') diff --git a/test cases/common/192 same target name flat layout/foo.c b/test cases/common/192 same target name flat layout/foo.c new file mode 100644 index 0000000..ed42789 --- /dev/null +++ b/test cases/common/192 same target name flat layout/foo.c @@ -0,0 +1 @@ +int meson_test_main_foo(void) { return 10; } diff --git a/test cases/common/192 same target name flat layout/main.c b/test cases/common/192 same target name flat layout/main.c new file mode 100644 index 0000000..6f02aeb --- /dev/null +++ b/test cases/common/192 same target name flat layout/main.c @@ -0,0 +1,16 @@ +#include <stdio.h> + +int meson_test_main_foo(void); +int meson_test_subproj_foo(void); + +int main(void) { + if (meson_test_main_foo() != 10) { + printf("Failed meson_test_main_foo\n"); + return 1; + } + if (meson_test_subproj_foo() != 20) { + printf("Failed meson_test_subproj_foo\n"); + return 1; + } + return 0; +} diff --git a/test cases/common/192 same target name flat layout/meson.build b/test cases/common/192 same target name flat layout/meson.build new file mode 100644 index 0000000..a3c95fa --- /dev/null +++ b/test cases/common/192 same target name flat layout/meson.build @@ -0,0 +1,11 @@ +project('subdir targets', 'c') + +# Idea behind this test is to create targets with identical name +# but different output files. We can do this by choosing different +# name_prefix of libraries. Target id does not depend on name_prefix. + +main_foo = static_library('foo', 'foo.c', name_prefix : 'main') +subdir('subdir') # defines subdir_foo + +exe = executable('prog', 'main.c', link_with : [main_foo, subdir_foo]) +test('main test', exe) diff --git a/test cases/common/192 same target name flat layout/subdir/foo.c b/test cases/common/192 same target name flat layout/subdir/foo.c new file mode 100644 index 0000000..f334292 --- /dev/null +++ b/test cases/common/192 same target name flat layout/subdir/foo.c @@ -0,0 +1 @@ +int meson_test_subproj_foo(void) { return 20; } diff --git a/test cases/common/192 same target name flat layout/subdir/meson.build b/test cases/common/192 same target name flat layout/subdir/meson.build new file mode 100644 index 0000000..223a5ef --- /dev/null +++ b/test cases/common/192 same target name flat layout/subdir/meson.build @@ -0,0 +1 @@ +subdir_foo = static_library('foo', 'foo.c', name_prefix : 'subdir') diff --git a/test cases/common/193 find override/meson.build b/test cases/common/193 find override/meson.build new file mode 100644 index 0000000..3b8af80 --- /dev/null +++ b/test cases/common/193 find override/meson.build @@ -0,0 +1,12 @@ +project('find program override', 'c') + +gencodegen = find_program('gencodegen', required : false) + +assert(not gencodegen.found(), 'gencodegen is an internal program, should not be found') + +# Test the check-if-found-else-override workflow +if not gencodegen.found() + subdir('subdir') +endif + +subdir('otherdir') diff --git a/test cases/common/193 find override/otherdir/main.c b/test cases/common/193 find override/otherdir/main.c new file mode 100644 index 0000000..2cef67c --- /dev/null +++ b/test cases/common/193 find override/otherdir/main.c @@ -0,0 +1,5 @@ +int be_seeing_you(); + +int main(int argc, char **argv) { + return be_seeing_you() == 6 ? 0 : 1; +} diff --git a/test cases/common/193 find override/otherdir/main2.c b/test cases/common/193 find override/otherdir/main2.c new file mode 100644 index 0000000..6d71688 --- /dev/null +++ b/test cases/common/193 find override/otherdir/main2.c @@ -0,0 +1,5 @@ +int number_returner(); + +int main(int argc, char **argv) { + return number_returner() == 100 ? 0 : 1; +} diff --git a/test cases/common/193 find override/otherdir/meson.build b/test cases/common/193 find override/otherdir/meson.build new file mode 100644 index 0000000..dc41f5b --- /dev/null +++ b/test cases/common/193 find override/otherdir/meson.build @@ -0,0 +1,26 @@ +gen = find_program('codegen') # Should use overridden value set in "subdir". + +src = custom_target('arrival', + input : 'source.desc', + output : 'file.c', + command : [gen, '@INPUT@', '@OUTPUT@'] + ) + +e = executable('six', 'main.c', src) + +test('six', e) + +# The same again, but this time with a program that was genererated +# with configure_file. + +gen = find_program('gencodegen') + +src = custom_target('hundred', + input : 'source2.desc', + output : 'file2.c', + command : [gen, '@INPUT@', '@OUTPUT@'] + ) + +e = executable('hundred', 'main2.c', src) + +test('hundred', e) diff --git a/test cases/common/193 find override/otherdir/source.desc b/test cases/common/193 find override/otherdir/source.desc new file mode 100644 index 0000000..8b19c9c --- /dev/null +++ b/test cases/common/193 find override/otherdir/source.desc @@ -0,0 +1 @@ +be_seeing_you diff --git a/test cases/common/193 find override/otherdir/source2.desc b/test cases/common/193 find override/otherdir/source2.desc new file mode 100644 index 0000000..965f868 --- /dev/null +++ b/test cases/common/193 find override/otherdir/source2.desc @@ -0,0 +1 @@ +number_returner diff --git a/test cases/common/193 find override/subdir/converter.py b/test cases/common/193 find override/subdir/converter.py new file mode 100755 index 0000000..ee2ff85 --- /dev/null +++ b/test cases/common/193 find override/subdir/converter.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import sys +import pathlib + +[ifilename, ofilename] = sys.argv[1:3] + +ftempl = '''int %s() { + return 6; +} +''' + +d = pathlib.Path(ifilename).read_text().split('\n')[0].strip() + +pathlib.Path(ofilename).write_text(ftempl % d) diff --git a/test cases/common/193 find override/subdir/gencodegen.py.in b/test cases/common/193 find override/subdir/gencodegen.py.in new file mode 100755 index 0000000..57d9c40 --- /dev/null +++ b/test cases/common/193 find override/subdir/gencodegen.py.in @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import sys +import pathlib + +[ifilename, ofilename] = sys.argv[1:3] + +ftempl = '''int %s() { + return @NUMBER@; +} +''' + +d = pathlib.Path(ifilename).read_text().split('\n')[0].strip() + +pathlib.Path(ofilename).write_text(ftempl % d) diff --git a/test cases/common/193 find override/subdir/meson.build b/test cases/common/193 find override/subdir/meson.build new file mode 100644 index 0000000..e5de34d --- /dev/null +++ b/test cases/common/193 find override/subdir/meson.build @@ -0,0 +1,14 @@ +x = find_program('converter.py') + +meson.override_find_program('codegen', x) + +# Override a command with a generated script + +cdata = configuration_data() + +cdata.set('NUMBER', 100) +numprog = configure_file(input : 'gencodegen.py.in', + output : 'gencodegen.py', + configuration : cdata) + +meson.override_find_program('gencodegen', numprog) diff --git a/test cases/common/194 partial dependency/declare_dependency/headers/foo.c b/test cases/common/194 partial dependency/declare_dependency/headers/foo.c new file mode 100644 index 0000000..215112c --- /dev/null +++ b/test cases/common/194 partial dependency/declare_dependency/headers/foo.c @@ -0,0 +1,16 @@ +/* Copyright © 2018 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. + */ + +#error "Included C sources that shouldn't be." diff --git a/test cases/common/194 partial dependency/declare_dependency/headers/foo.h b/test cases/common/194 partial dependency/declare_dependency/headers/foo.h new file mode 100644 index 0000000..28c81c9 --- /dev/null +++ b/test cases/common/194 partial dependency/declare_dependency/headers/foo.h @@ -0,0 +1,16 @@ +/* Copyright © 2018 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. + */ + +int foo(void); diff --git a/test cases/common/194 partial dependency/declare_dependency/main.c b/test cases/common/194 partial dependency/declare_dependency/main.c new file mode 100644 index 0000000..e9ed032 --- /dev/null +++ b/test cases/common/194 partial dependency/declare_dependency/main.c @@ -0,0 +1,25 @@ +/* Copyright © 2018 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. + */ + +#include "foo.h" + +int main() { + int a = foo(); + if (a == 1) { + return 0; + } else { + return 1; + } +} diff --git a/test cases/common/194 partial dependency/declare_dependency/meson.build b/test cases/common/194 partial dependency/declare_dependency/meson.build new file mode 100644 index 0000000..86e2608 --- /dev/null +++ b/test cases/common/194 partial dependency/declare_dependency/meson.build @@ -0,0 +1,28 @@ +# Copyright © 2018 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. + +dec_dep = declare_dependency( + sources : files('headers/foo.c'), + include_directories : include_directories('headers'), +) + +sub_dep = dec_dep.partial_dependency(includes : true) + +dec_exe = executable( + 'declare_dep', + files('main.c', 'other.c'), + dependencies : sub_dep, +) + +test('Declare Dependency', dec_exe) diff --git a/test cases/common/194 partial dependency/declare_dependency/other.c b/test cases/common/194 partial dependency/declare_dependency/other.c new file mode 100644 index 0000000..b1e199e --- /dev/null +++ b/test cases/common/194 partial dependency/declare_dependency/other.c @@ -0,0 +1,20 @@ +/* Copyright © 2018 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. + */ + +#include "foo.h" + +int foo(void) { + return 1; +} diff --git a/test cases/common/194 partial dependency/meson.build b/test cases/common/194 partial dependency/meson.build new file mode 100644 index 0000000..e908487 --- /dev/null +++ b/test cases/common/194 partial dependency/meson.build @@ -0,0 +1,17 @@ +# Copyright © 2018 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('partial dependency', ['c', 'cpp']) + +subdir('declare_dependency') diff --git a/test cases/common/195 openmp/main.c b/test cases/common/195 openmp/main.c new file mode 100644 index 0000000..cc81f48 --- /dev/null +++ b/test cases/common/195 openmp/main.c @@ -0,0 +1,16 @@ +#include <stdio.h> +#include <omp.h> + +int main(void) { +#ifdef _OPENMP + if (omp_get_max_threads() == 2) { + return 0; + } else { + printf("Max threads is %d not 2.\n", omp_get_max_threads()); + return 1; + } +#else + printf("_OPENMP is not defined; is OpenMP compilation working?\n"); + return 1; +#endif +} diff --git a/test cases/common/195 openmp/main.cpp b/test cases/common/195 openmp/main.cpp new file mode 100644 index 0000000..b12be3f --- /dev/null +++ b/test cases/common/195 openmp/main.cpp @@ -0,0 +1,16 @@ +#include <iostream> +#include <omp.h> + +int main(void) { +#ifdef _OPENMP + if (omp_get_max_threads() == 2) { + return 0; + } else { + std::cout << "Max threads is " << omp_get_max_threads() << " not 2." << std::endl; + return 1; + } +#else + printf("_OPENMP is not defined; is OpenMP compilation working?\n"); + return 1; +#endif +} diff --git a/test cases/common/195 openmp/main.f90 b/test cases/common/195 openmp/main.f90 new file mode 100644 index 0000000..c062d86 --- /dev/null +++ b/test cases/common/195 openmp/main.f90 @@ -0,0 +1,8 @@ +program main + if (omp_get_max_threads() .eq. 2) then + stop 0 + else + print *, 'Max threads is', omp_get_max_threads(), 'not 2.' + stop 1 + endif +end program main diff --git a/test cases/common/195 openmp/meson.build b/test cases/common/195 openmp/meson.build new file mode 100644 index 0000000..a05ca59 --- /dev/null +++ b/test cases/common/195 openmp/meson.build @@ -0,0 +1,40 @@ +project('openmp', 'c', 'cpp') + +cc = meson.get_compiler('c') +if cc.get_id() == 'gcc' and cc.version().version_compare('<4.2.0') + error('MESON_SKIP_TEST gcc is too old to support OpenMP.') +endif +if cc.get_id() == 'clang' and cc.version().version_compare('<3.7.0') + error('MESON_SKIP_TEST clang is too old to support OpenMP.') +endif +if cc.get_id() == 'msvc' and cc.version().version_compare('<17') + error('MESON_SKIP_TEST msvc is too old to support OpenMP.') +endif +if host_machine.system() == 'darwin' + error('MESON_SKIP_TEST macOS does not support OpenMP.') +endif + +openmp = dependency('openmp') + +exec = executable('exec', + 'main.c', + dependencies : [openmp]) + +execpp = executable('execpp', + 'main.cpp', + dependencies : [openmp]) + +env = environment() +env.set('OMP_NUM_THREADS', '2') + +test('OpenMP C', exec, env : env) +test('OpenMP C++', execpp, env : env) + + +if add_languages('fortran', required : false) + exef = executable('exef', + 'main.f90', + dependencies : [openmp]) + + test('OpenMP Fortran', execpp, env : env) +endif diff --git a/test cases/common/22 header in file list/meson.build b/test cases/common/22 header in file list/meson.build index cc30c71..ff42cc4 100644 --- a/test cases/common/22 header in file list/meson.build +++ b/test cases/common/22 header in file list/meson.build @@ -1,4 +1,14 @@ project('header in file list', 'c') +cc_id = meson.get_compiler('c').get_id() +cc_ver = meson.get_compiler('c').version() + +if cc_id == 'intel' or (cc_id == 'lcc' and cc_ver.version_compare('<=1.23.08') + # ICC and LCC <= 1.23.08 do not escape spaces in paths in the dependency file, so Ninja + # (correctly) thinks that the rule has multiple outputs and errors out: + # 'depfile has multiple output paths' + error('MESON_SKIP_TEST: Skipping test because your compiler is known to generate broken dependency files') +endif + exe = executable('prog', 'prog.c', 'header.h') test('basic', exe) diff --git a/test cases/common/33 try compile/meson.build b/test cases/common/33 try compile/meson.build index 09ca395..cb1037d 100644 --- a/test cases/common/33 try compile/meson.build +++ b/test cases/common/33 try compile/meson.build @@ -1,11 +1,11 @@ project('try compile', 'c', 'cpp') code = '''#include<stdio.h> -void func() { printf("Something.\n"); } +void func() { printf("Something.\\n"); } ''' breakcode = '''#include<nonexisting.h> -void func() { printf("This won't work.\n"); } +void func() { printf("This won't work.\\n"); } ''' foreach compiler : [meson.get_compiler('c'), meson.get_compiler('cpp')] diff --git a/test cases/common/39 tryrun/meson.build b/test cases/common/39 tryrun/meson.build index c64446f..daf5be7 100644 --- a/test cases/common/39 tryrun/meson.build +++ b/test cases/common/39 tryrun/meson.build @@ -13,8 +13,8 @@ endif ok_code = '''#include<stdio.h> int main(int argc, char **argv) { - printf("%s\n", "stdout"); - fprintf(stderr, "%s\n", "stderr"); + printf("%s\\n", "stdout"); + fprintf(stderr, "%s\\n", "stderr"); return 0; } ''' diff --git a/test cases/common/42 string operations/meson.build b/test cases/common/42 string operations/meson.build index a43de70..1c289eb 100644 --- a/test cases/common/42 string operations/meson.build +++ b/test cases/common/42 string operations/meson.build @@ -77,21 +77,21 @@ assert('"1.1.20"'.strip('"') == '1.1.20', '" badly stripped') assert('"1.1.20"'.strip('".') == '1.1.20', '". badly stripped') assert('"1.1.20" '.strip('" ') == '1.1.20', '". badly stripped') -bs_b = '''\b''' -bs_bs_b = '''\\b''' +bs_c = '''\c''' +bs_bs_c = '''\\\c''' nl = ''' ''' -bs_n = '''\n''' +bs_n = '''\\n''' bs_nl = '''\ ''' -bs_bs_n = '''\\n''' -bs_bs_nl = '''\\ +bs_bs_n = '''\\\\n''' +bs_bs_nl = '''\\\\ ''' -assert('\b' == bs_b, 'Single backslash broken') -assert('\\b' == bs_b, 'Double backslash broken') -assert('\\\b' == bs_bs_b, 'Three backslash broken') -assert('\\\\b' == bs_bs_b, 'Four backslash broken') +assert('\c' == bs_c, 'Single backslash broken') +assert('\\c' == bs_c, 'Double backslash broken') +assert('\\\c' == bs_bs_c, 'Three backslash broken') +assert('\\\\c' == bs_bs_c, 'Four backslash broken') assert('\n' == nl, 'Newline escape broken') assert('\\n' == bs_n, 'Double backslash broken before n') assert('\\\n' == bs_nl, 'Three backslash broken before n') diff --git a/test cases/common/51 pkgconfig-gen/dependencies/meson.build b/test cases/common/51 pkgconfig-gen/dependencies/meson.build index 2e00943..047e7e7 100644 --- a/test cases/common/51 pkgconfig-gen/dependencies/meson.build +++ b/test cases/common/51 pkgconfig-gen/dependencies/meson.build @@ -1,20 +1,17 @@ -project('pkgconfig-gen-dependencies', 'c') +project('pkgconfig-gen-dependencies', 'c', version: '1.0') pkgg = import('pkgconfig') # libmain internally use libinternal and expose libexpose in its API exposed_lib = shared_library('libexposed', 'exposed.c') internal_lib = shared_library('libinternal', 'internal.c') -main_lib = static_library('libmain', link_with : [exposed_lib, internal_lib]) +main_lib = both_libraries('libmain', link_with : [exposed_lib, internal_lib]) -pkgg.generate(libraries : exposed_lib, - version : '1.0', - name : 'libexposed', - description : 'An exposed library in dependency test.' -) +pkgg.generate(exposed_lib) # Declare a few different Dependency objects -pc_dep = dependency('libfoo') +pc_dep = dependency('libfoo', version : '>=1.0') +pc_dep_dup = dependency('libfoo', version : '>= 1.0') notfound_dep = dependency('notfound', required : false) threads_dep = dependency('threads') custom_dep = declare_dependency(link_args : ['-lcustom'], compile_args : ['-DCUSTOM']) @@ -28,9 +25,10 @@ custom2_dep = declare_dependency(link_args : ['-lcustom2'], compile_args : ['-DC # - Having custom_dep in libraries and libraries_private should only add it in Libs # - Having custom2_dep in libraries_private should not add its Cflags # - Having pc_dep in libraries_private should add it in Requires.private +# - pc_dep_dup is the same library and same version, should be ignored # - notfound_dep is not required so it shouldn't appear in the pc file. pkgg.generate(libraries : [main_lib, exposed_lib, threads_dep , custom_dep], - libraries_private : [custom_dep, custom2_dep, pc_dep, notfound_dep], + libraries_private : [custom_dep, custom2_dep, pc_dep, pc_dep_dup, notfound_dep], version : '1.0', name : 'dependency-test', filebase : 'dependency-test', diff --git a/test cases/common/64 custom header generator/meson.build b/test cases/common/64 custom header generator/meson.build index 33ba4c5..2279513 100644 --- a/test cases/common/64 custom header generator/meson.build +++ b/test cases/common/64 custom header generator/meson.build @@ -1,5 +1,15 @@ project('custom header generator', 'c') +cc_id = meson.get_compiler('c').get_id() +cc_ver = meson.get_compiler('c').version() + +if cc_id == 'intel' or (cc_id == 'lcc' and cc_ver.version_compare('<=1.23.08') + # ICC and LCC <= 1.23.08 do not escape spaces in paths in the dependency file, so Ninja + # (correctly) thinks that the rule has multiple outputs and errors out: + # 'depfile has multiple output paths' + error('MESON_SKIP_TEST: Skipping test because your compiler is known to generate broken dependency files') +endif + gen = find_program('makeheader.py') generated_h = custom_target('makeheader.py', diff --git a/test cases/d/3 shared library/meson.build b/test cases/d/3 shared library/meson.build index 78ad766..4616242 100644 --- a/test cases/d/3 shared library/meson.build +++ b/test cases/d/3 shared library/meson.build @@ -10,3 +10,12 @@ endif ldyn = shared_library('stuff', 'libstuff.d', install : true) ed = executable('app_d', 'app.d', link_with : ldyn, install : true) test('linktest_dyn', ed) + +# test D attributes for pkg-config +pkgc = import('pkgconfig') +pkgc.generate(name: 'test', + libraries: ldyn, + subdirs: 'd/stuff', + description: 'A test of D attributes to pkgconfig.generate.', + d_module_versions: ['Use_Static'] +) diff --git a/test cases/d/6 unittest/app.d b/test cases/d/6 unittest/app.d index 751e754..71c6414 100644 --- a/test cases/d/6 unittest/app.d +++ b/test cases/d/6 unittest/app.d @@ -23,10 +23,14 @@ unittest { writeln ("TEST"); import core.stdc.stdlib : exit; + import second_unit; assert (getFour () > 2); assert (getFour () == 4); + // this is a regression test for https://github.com/mesonbuild/meson/issues/3337 + secondModuleTestFunc (); + // we explicitly terminate here to give the unittest program a different exit // code than the main application has. // (this prevents the regular main() from being executed) diff --git a/test cases/d/6 unittest/meson.build b/test cases/d/6 unittest/meson.build index 1551e94..49a0700 100644 --- a/test cases/d/6 unittest/meson.build +++ b/test cases/d/6 unittest/meson.build @@ -1,8 +1,8 @@ project('D Unittests', 'd') -e = executable('dapp', 'app.d', install : true) +e = executable('dapp', ['app.d', 'second_unit.d'], install : true) test('dapp_run', e, should_fail: true) -e_test = executable('dapp_test', 'app.d', - d_args: meson.get_compiler('d').unittest_args()) +e_test = executable('dapp_test', ['app.d', 'second_unit.d'], + d_unittest: true) test('dapp_test', e_test) diff --git a/test cases/d/6 unittest/second_unit.d b/test cases/d/6 unittest/second_unit.d new file mode 100644 index 0000000..fdb62a9 --- /dev/null +++ b/test cases/d/6 unittest/second_unit.d @@ -0,0 +1,10 @@ + +void secondModuleTestFunc () +{ + import std.stdio : writeln; + + version (unittest) + writeln ("Hello!"); + else + assert (0); +} diff --git a/test cases/d/9 features/app.d b/test cases/d/9 features/app.d index 37cc1dd..6b43bf0 100644 --- a/test cases/d/9 features/app.d +++ b/test cases/d/9 features/app.d @@ -3,6 +3,8 @@ import std.stdio; import std.array : split; import std.string : strip; +import extra; + auto getMenu () { auto foods = import ("food.txt").strip.split ("\n"); @@ -31,7 +33,12 @@ void main (string[] args) version (With_People) { if (request == "people") { writeln ("People: ", getPeople.join (", ")); - exit (0); + + // only exit successfully if the second module also had its module version set. + // this checks for issue https://github.com/mesonbuild/meson/issues/3337 + if (secondModulePeopleVersionSet ()) + exit (0); + exit (1); } } diff --git a/test cases/d/9 features/extra.d b/test cases/d/9 features/extra.d new file mode 100644 index 0000000..832b292 --- /dev/null +++ b/test cases/d/9 features/extra.d @@ -0,0 +1,9 @@ + +auto secondModulePeopleVersionSet () +{ + version (With_People) { + return true; + } else { + return false; + } +} diff --git a/test cases/d/9 features/meson.build b/test cases/d/9 features/meson.build index 356e9f3..694e488 100644 --- a/test cases/d/9 features/meson.build +++ b/test cases/d/9 features/meson.build @@ -6,8 +6,10 @@ project('D Features', 'd') # STRINGS TO PATHS MANUALLY! data_dir = join_paths(meson.current_source_dir(), 'data') +test_src = ['app.d', 'extra.d'] + e_plain_bcompat = executable('dapp_menu_bcompat', - 'app.d', + test_src, d_import_dirs: [data_dir] ) test('dapp_menu_t_fail_bcompat', e_plain_bcompat, should_fail: true) @@ -18,7 +20,7 @@ test('dapp_menu_t_bcompat', e_plain_bcompat, args: ['menu']) data_dir = include_directories('data') e_plain = executable('dapp_menu', - 'app.d', + test_src, d_import_dirs: [data_dir] ) test('dapp_menu_t_fail', e_plain, should_fail: true) @@ -27,7 +29,7 @@ test('dapp_menu_t', e_plain, args: ['menu']) # test feature versions and string imports e_versions = executable('dapp_versions', - 'app.d', + test_src, d_import_dirs: [data_dir], d_module_versions: ['No_Menu', 'With_People'] ) @@ -36,7 +38,7 @@ test('dapp_versions_t', e_versions, args: ['people']) # test everything and unittests e_test = executable('dapp_test', - 'app.d', + test_src, d_import_dirs: [data_dir], d_module_versions: ['No_Menu', 'With_People'], d_unittest: true diff --git a/test cases/failing/17 same target/file.c b/test cases/failing/17 same target/file.c new file mode 100644 index 0000000..7412372 --- /dev/null +++ b/test cases/failing/17 same target/file.c @@ -0,0 +1 @@ +int func() { return 0; } diff --git a/test cases/failing/17 same target/meson.build b/test cases/failing/17 same target/meson.build new file mode 100644 index 0000000..ee586d0 --- /dev/null +++ b/test cases/failing/17 same target/meson.build @@ -0,0 +1,4 @@ +project('same target', 'c') + +static_library('foo', 'file.c') +static_library('foo', 'file.c') diff --git a/test cases/failing/71 skip only subdir/meson.build b/test cases/failing/71 skip only subdir/meson.build new file mode 100644 index 0000000..4832bd4 --- /dev/null +++ b/test cases/failing/71 skip only subdir/meson.build @@ -0,0 +1,8 @@ +# Check that skip_rest only exits subdir, not the whole script. +# Should create an error because main.cpp does not exists. +project('example exit', 'cpp') + +subdir('subdir') + +message('Good') +executable('main', 'main.cpp') diff --git a/test cases/failing/71 skip only subdir/subdir/meson.build b/test cases/failing/71 skip only subdir/subdir/meson.build new file mode 100644 index 0000000..1ba447b --- /dev/null +++ b/test cases/failing/71 skip only subdir/subdir/meson.build @@ -0,0 +1,3 @@ +subdir_done() + +error('Unreachable') diff --git a/test cases/failing/72 invalid escape char/meson.build b/test cases/failing/72 invalid escape char/meson.build new file mode 100644 index 0000000..b4e9196 --- /dev/null +++ b/test cases/failing/72 invalid escape char/meson.build @@ -0,0 +1,4 @@ +# Make sure meson exits on invalid string +# The string below contains an invalid unicode code point + +'my name is what \uxyzo who are you' diff --git a/test cases/failing/73 dual override/meson.build b/test cases/failing/73 dual override/meson.build new file mode 100644 index 0000000..e5f86ba --- /dev/null +++ b/test cases/failing/73 dual override/meson.build @@ -0,0 +1,5 @@ +project('yo dawg', 'c') + +p = find_program('overrides.py') +meson.override_find_program('override', p) +meson.override_find_program('override', p) diff --git a/test cases/failing/73 dual override/overrides.py b/test cases/failing/73 dual override/overrides.py new file mode 100644 index 0000000..49e9b7a --- /dev/null +++ b/test cases/failing/73 dual override/overrides.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 + +print('Yo dawg, we put overrides in your overrides,') +print('so now you can override when you override.') diff --git a/test cases/failing/74 override used/meson.build b/test cases/failing/74 override used/meson.build new file mode 100644 index 0000000..61885bb --- /dev/null +++ b/test cases/failing/74 override used/meson.build @@ -0,0 +1,5 @@ +project('overridde an already found exe', 'c') + +old = find_program('something.py') +replacement = find_program('other.py') +meson.override_find_program('something.py', replacement) diff --git a/test cases/failing/74 override used/other.py b/test cases/failing/74 override used/other.py new file mode 100755 index 0000000..f62ba96 --- /dev/null +++ b/test cases/failing/74 override used/other.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +print('Doing something else.') diff --git a/test cases/failing/74 override used/something.py b/test cases/failing/74 override used/something.py new file mode 100755 index 0000000..64c9454 --- /dev/null +++ b/test cases/failing/74 override used/something.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +print('Doing something.') diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build index df55b30..9399598 100644 --- a/test cases/frameworks/1 boost/meson.build +++ b/test cases/frameworks/1 boost/meson.build @@ -31,3 +31,5 @@ test('Boost statictest', staticexe) test('Boost UTF test', unitexe) test('Boost nomod', nomodexe) test('Boost extralib test', extralibexe) + +subdir('partial_dep') diff --git a/test cases/frameworks/1 boost/partial_dep/foo.cpp b/test cases/frameworks/1 boost/partial_dep/foo.cpp new file mode 100644 index 0000000..da58703 --- /dev/null +++ b/test cases/frameworks/1 boost/partial_dep/foo.cpp @@ -0,0 +1,20 @@ +/* Copyright © 2018 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. + */ + +#include "foo.hpp" + +vec Foo::vector() { + return myvec; +} diff --git a/test cases/frameworks/1 boost/partial_dep/foo.hpp b/test cases/frameworks/1 boost/partial_dep/foo.hpp new file mode 100644 index 0000000..393d3f6 --- /dev/null +++ b/test cases/frameworks/1 boost/partial_dep/foo.hpp @@ -0,0 +1,27 @@ +/* Copyright © 2018 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. + */ + +#include <boost/fusion/container/vector.hpp> + +typedef boost::fusion::vector<int> vec; + + +class Foo { + public: + Foo() {}; + vec vector(); + private: + const vec myvec = vec(4); +}; diff --git a/test cases/frameworks/1 boost/partial_dep/main.cpp b/test cases/frameworks/1 boost/partial_dep/main.cpp new file mode 100644 index 0000000..5138f5c --- /dev/null +++ b/test cases/frameworks/1 boost/partial_dep/main.cpp @@ -0,0 +1,28 @@ +/* Copyright © 2018 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. + */ + +#include <iostream> +#include <boost/fusion/include/at_c.hpp> +#include "foo.hpp" + + +int main() { + auto foo = Foo(); + vec v = foo.vector(); + std::cout << boost::fusion::at_c<0>(v) << std::endl; + + return 0; +} + diff --git a/test cases/frameworks/1 boost/partial_dep/meson.build b/test cases/frameworks/1 boost/partial_dep/meson.build new file mode 100644 index 0000000..9d481bb --- /dev/null +++ b/test cases/frameworks/1 boost/partial_dep/meson.build @@ -0,0 +1,31 @@ +# Copyright © 2018 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. + +dep_boost = dependency('boost') +dep_boost_headers = dep_boost.partial_dependency(compile_args : true) + +libfoo = static_library( + 'foo', + 'foo.cpp', + dependencies : dep_boost_headers, +) + +exe_external_dep = executable( + 'external_dep', + 'main.cpp', + dependencies : dep_boost, + link_with : libfoo +) + +test('External Dependency', exe_external_dep) diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index b508df3..e8d12b6 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -56,7 +56,7 @@ foreach qt : ['qt4', 'qt5'] endif # Test that setting a unique name with a positional argument works - qtmodule.preprocess(qt + 'teststuff', qresources : ['stuff.qrc', 'stuff2.qrc'], method : get_option('method')) + qtmodule.preprocess(qt + 'teststuff', qresources : files(['stuff.qrc', 'stuff2.qrc']), method : get_option('method')) qexe = executable(qt + 'app', sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing. diff --git a/test cases/frameworks/4 qt/subfolder/generator.py b/test cases/frameworks/4 qt/subfolder/generator.py new file mode 100644 index 0000000..045d99a --- /dev/null +++ b/test cases/frameworks/4 qt/subfolder/generator.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import sys + +if len(sys.argv) > 1: + with open(sys.argv[1], "w") as output: + output.write("Hello World") diff --git a/test cases/frameworks/4 qt/subfolder/main.cpp b/test cases/frameworks/4 qt/subfolder/main.cpp index 61cc9d4..9661811 100644 --- a/test cases/frameworks/4 qt/subfolder/main.cpp +++ b/test cases/frameworks/4 qt/subfolder/main.cpp @@ -1,9 +1,28 @@ #include <QImage> +#include <QFile> +#include <QString> int main(int argc, char **argv) { + #ifndef UNITY_BUILD Q_INIT_RESOURCE(stuff3); - QImage qi(":/thing.png"); - if(qi.width() != 640) { + Q_INIT_RESOURCE(stuff4); + #endif + + for(auto fname:{":/thing.png", ":/thing4.png"}) + { + QImage img1(fname); + if(img1.width() != 640) { + return 1; + } + } + + for(auto fname:{":/txt_resource.txt",":/txt_resource2.txt"}) + { + QFile file(fname); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return 1; + QString line = file.readLine(); + if(line.compare("Hello World")) return 1; } return 0; diff --git a/test cases/frameworks/4 qt/subfolder/meson.build b/test cases/frameworks/4 qt/subfolder/meson.build index d3ff609..f1b84e6 100644 --- a/test cases/frameworks/4 qt/subfolder/meson.build +++ b/test cases/frameworks/4 qt/subfolder/meson.build @@ -1,4 +1,32 @@ -qresources = qtmodule.preprocess(qresources : 'resources/stuff3.qrc') +simple_gen = find_program('generator.py', required : true) -app = executable('subfolder', 'main.cpp', qresources, dependencies : qtdep) +txt_resource = custom_target('txt_resource', + output : 'txt_resource.txt', + command : [simple_gen, '@OUTPUT@'], +) + +cfg = configuration_data() + +cfg.set('filepath', meson.current_source_dir()+'/../thing2.png') +cfg.set('txt_resource', txt_resource.full_path()) +# here we abuse the system by guessing build dir layout +cfg.set('txt_resource2', 'txt_resource.txt') + + +rc_file = configure_file( + configuration : cfg, + input : 'resources/stuff4.qrc.in', + output : 'stuff4.qrc', +) + +extra_cpp_args = [] +if meson.is_unity() + extra_cpp_args += '-DUNITY_BUILD' + qresources = qtmodule.preprocess(qt + '_subfolder_unity_ressource',qresources : ['resources/stuff3.qrc', rc_file]) +else + qresources = qtmodule.preprocess(qresources : ['resources/stuff3.qrc', rc_file]) +endif + +app = executable('subfolder', 'main.cpp', qresources, dependencies : qtdep, cpp_args: extra_cpp_args) +test(qt + 'subfolder', app) diff --git a/test cases/frameworks/4 qt/subfolder/resources/stuff4.qrc.in b/test cases/frameworks/4 qt/subfolder/resources/stuff4.qrc.in new file mode 100644 index 0000000..c30a358 --- /dev/null +++ b/test cases/frameworks/4 qt/subfolder/resources/stuff4.qrc.in @@ -0,0 +1,8 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource> + <file alias="thing4.png">@filepath@</file> + <file alias="txt_resource.txt">@txt_resource@</file> + <file alias="txt_resource2.txt">@txt_resource2@</file> + </qresource> +</RCC> diff --git a/test cases/frameworks/7 gnome/gdbus/meson.build b/test cases/frameworks/7 gnome/gdbus/meson.build index ea91caa..57d7f23 100644 --- a/test cases/frameworks/7 gnome/gdbus/meson.build +++ b/test cases/frameworks/7 gnome/gdbus/meson.build @@ -1,3 +1,12 @@ +gdbus_src = gnome.gdbus_codegen('generated-gdbus-no-docbook', 'com.example.Sample.xml', + interface_prefix : 'com.example.', + namespace : 'Sample', + annotations : [ + ['com.example.Hello()', 'org.freedesktop.DBus.Deprecated', 'true'] + ], +) +assert(gdbus_src.length() == 2, 'expected 2 targets') + gdbus_src = gnome.gdbus_codegen('generated-gdbus', 'com.example.Sample.xml', interface_prefix : 'com.example.', namespace : 'Sample', @@ -6,6 +15,7 @@ gdbus_src = gnome.gdbus_codegen('generated-gdbus', 'com.example.Sample.xml', ], docbook : 'generated-gdbus-doc' ) +assert(gdbus_src.length() == 3, 'expected 3 targets') gdbus_exe = executable('gdbus-test', 'gdbusprog.c', gdbus_src, diff --git a/test cases/frameworks/7 gnome/installed_files.txt b/test cases/frameworks/7 gnome/installed_files.txt index c7c704f..ac132ef 100644 --- a/test cases/frameworks/7 gnome/installed_files.txt +++ b/test cases/frameworks/7 gnome/installed_files.txt @@ -1,6 +1,7 @@ usr/include/enums.h usr/include/enums2.h usr/include/enums3.h +usr/include/enums5.h usr/include/marshaller.h usr/lib/?libgir_lib.so usr/lib/?libdep1lib.so diff --git a/test cases/frameworks/7 gnome/mkenums/meson.build b/test cases/frameworks/7 gnome/mkenums/meson.build index 9963455..44c21cb 100644 --- a/test cases/frameworks/7 gnome/mkenums/meson.build +++ b/test cases/frameworks/7 gnome/mkenums/meson.build @@ -123,6 +123,7 @@ enums4 = gnome.mkenums_simple('enums4', sources : 'meson-sample.h', enumexe4 = executable('enumprog4', 'main4.c', enums4, dependencies : gobj) enums5 = gnome.mkenums_simple('enums5', sources : 'meson-sample.h', + install_header : true, decorator : 'MESON_EXPORT', header_prefix : '#include "meson-decls.h"') enumexe5 = executable('enumprog5', main, enums5, dependencies : gobj) diff --git a/test cases/python/1 extmodule/blaster.py b/test cases/python/1 extmodule/blaster.py new file mode 100755 index 0000000..163b6d4 --- /dev/null +++ b/test cases/python/1 extmodule/blaster.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +import sys +import tachyon + +result = tachyon.phaserize('shoot') + +if not isinstance(result, int): + print('Returned result not an integer.') + sys.exit(1) + +if result != 1: + print('Returned result {} is not 1.'.format(result)) + sys.exit(1) diff --git a/test cases/python/1 extmodule/ext/meson.build b/test cases/python/1 extmodule/ext/meson.build new file mode 100644 index 0000000..b13bb32 --- /dev/null +++ b/test cases/python/1 extmodule/ext/meson.build @@ -0,0 +1,6 @@ +pylib = py.extension_module('tachyon', + 'tachyon_module.c', + dependencies : py_dep, +) + +pypathdir = meson.current_build_dir() diff --git a/test cases/python/1 extmodule/ext/tachyon_module.c b/test cases/python/1 extmodule/ext/tachyon_module.c new file mode 100644 index 0000000..68eda53 --- /dev/null +++ b/test cases/python/1 extmodule/ext/tachyon_module.c @@ -0,0 +1,59 @@ +/* + Copyright 2018 The Meson development team + + 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. +*/ + +/* A very simple Python extension module. */ + +#include <Python.h> +#include <string.h> + +static PyObject* phaserize(PyObject *self, PyObject *args) { + const char *message; + int result; + + if(!PyArg_ParseTuple(args, "s", &message)) + return NULL; + + result = strcmp(message, "shoot") ? 0 : 1; +#if PY_VERSION_HEX < 0x03000000 + return PyInt_FromLong(result); +#else + return PyLong_FromLong(result); +#endif +} + +static PyMethodDef TachyonMethods[] = { + {"phaserize", phaserize, METH_VARARGS, + "Shoot tachyon cannons."}, + {NULL, NULL, 0, NULL} +}; + +#if PY_VERSION_HEX < 0x03000000 +PyMODINIT_FUNC inittachyon(void) { + Py_InitModule("tachyon", TachyonMethods); +} +#else +static struct PyModuleDef tachyonmodule = { + PyModuleDef_HEAD_INIT, + "tachyon", + NULL, + -1, + TachyonMethods +}; + +PyMODINIT_FUNC PyInit_tachyon(void) { + return PyModule_Create(&tachyonmodule); +} +#endif diff --git a/test cases/python/1 extmodule/meson.build b/test cases/python/1 extmodule/meson.build new file mode 100644 index 0000000..4798654 --- /dev/null +++ b/test cases/python/1 extmodule/meson.build @@ -0,0 +1,23 @@ +project('Python extension module', 'c', + default_options : ['buildtype=release']) + +py_mod = import('python') + +py = py_mod.find_installation(get_option('python'), required : false) + +if py.found() + py_dep = py.dependency() + + if py_dep.found() + subdir('ext') + + test('extmod', + py, + args : files('blaster.py'), + env : ['PYTHONPATH=' + pypathdir]) + else + error('MESON_SKIP_TEST: Python libraries not found, skipping test.') + endif +else + error('MESON_SKIP_TEST: Python not found, skipping test.') +endif diff --git a/test cases/python/1 extmodule/meson_options.txt b/test cases/python/1 extmodule/meson_options.txt new file mode 100644 index 0000000..b8f645d --- /dev/null +++ b/test cases/python/1 extmodule/meson_options.txt @@ -0,0 +1,3 @@ +option('python', type: 'string', + description: 'Name of or path to the python executable' +) diff --git a/test cases/unit/13 reconfigure/meson.build b/test cases/unit/13 reconfigure/meson.build index 102180e..453644a 100644 --- a/test cases/unit/13 reconfigure/meson.build +++ b/test cases/unit/13 reconfigure/meson.build @@ -1,5 +1,5 @@ project('reconfigure test', ['c']) -if get_option('b_lto') != true - error('b_lto not set') +if get_option('b_coverage') != true + error('b_coverage not set') endif diff --git a/test cases/unit/26 guessed linker dependencies/exe/app.c b/test cases/unit/26 guessed linker dependencies/exe/app.c new file mode 100644 index 0000000..1031a42 --- /dev/null +++ b/test cases/unit/26 guessed linker dependencies/exe/app.c @@ -0,0 +1,6 @@ +void liba_func(); + +int main() { + liba_func(); + return 0; +} diff --git a/test cases/unit/26 guessed linker dependencies/exe/meson.build b/test cases/unit/26 guessed linker dependencies/exe/meson.build new file mode 100644 index 0000000..8bb1bd7 --- /dev/null +++ b/test cases/unit/26 guessed linker dependencies/exe/meson.build @@ -0,0 +1,7 @@ +project('exe', ['c']) + +executable('app', + 'app.c', + # Use uninterpreted strings to avoid path finding by dependency or compiler.find_library + link_args: ['-ltest-lib'] + ) diff --git a/test cases/unit/26 guessed linker dependencies/lib/lib.c b/test cases/unit/26 guessed linker dependencies/lib/lib.c new file mode 100644 index 0000000..1a8f94d --- /dev/null +++ b/test cases/unit/26 guessed linker dependencies/lib/lib.c @@ -0,0 +1,20 @@ +#if defined _WIN32 + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +void DLL_PUBLIC liba_func() { +} + +#ifdef MORE_EXPORTS + +void DLL_PUBLIC libb_func() { +} + +#endif diff --git a/test cases/unit/26 guessed linker dependencies/lib/meson.build b/test cases/unit/26 guessed linker dependencies/lib/meson.build new file mode 100644 index 0000000..36df112 --- /dev/null +++ b/test cases/unit/26 guessed linker dependencies/lib/meson.build @@ -0,0 +1,11 @@ +project('lib1', ['c']) + +c_args = [] + +# Microsoft's compiler is quite smart about touching import libs on changes, +# so ensure that there is actually a change in symbols. +if get_option('more_exports') + c_args += '-DMORE_EXPORTS' +endif + +a = library('test-lib', 'lib.c', c_args: c_args, install: true) diff --git a/test cases/unit/26 guessed linker dependencies/lib/meson_options.txt b/test cases/unit/26 guessed linker dependencies/lib/meson_options.txt new file mode 100644 index 0000000..2123e45 --- /dev/null +++ b/test cases/unit/26 guessed linker dependencies/lib/meson_options.txt @@ -0,0 +1 @@ +option('more_exports', type : 'boolean', value : false) diff --git a/test cases/unit/26 shared_mod linking/libfile.c b/test cases/unit/26 shared_mod linking/libfile.c new file mode 100644 index 0000000..44f7667 --- /dev/null +++ b/test cases/unit/26 shared_mod linking/libfile.c @@ -0,0 +1,14 @@ +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_PUBLIC __declspec(dllexport) +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +int DLL_PUBLIC func() { + return 0; +} diff --git a/test cases/unit/26 shared_mod linking/main.c b/test cases/unit/26 shared_mod linking/main.c new file mode 100644 index 0000000..12f9c98 --- /dev/null +++ b/test cases/unit/26 shared_mod linking/main.c @@ -0,0 +1,11 @@ +#if defined _WIN32 || defined __CYGWIN__ + #define DLL_IMPORT __declspec(dllimport) +#else + #define DLL_IMPORT +#endif + +int DLL_IMPORT func(); + +int main(int argc, char **arg) { + return func(); +} diff --git a/test cases/unit/26 shared_mod linking/meson.build b/test cases/unit/26 shared_mod linking/meson.build new file mode 100644 index 0000000..994a5d3 --- /dev/null +++ b/test cases/unit/26 shared_mod linking/meson.build @@ -0,0 +1,5 @@ +project('shared library linking test', 'c', 'cpp') + +mod = shared_module('mymod', 'libfile.c') + +exe = executable('prog', 'main.c', link_with : mod, install : true)
\ No newline at end of file diff --git a/test cases/unit/27 forcefallback/meson.build b/test cases/unit/27 forcefallback/meson.build new file mode 100644 index 0000000..e6a90ea --- /dev/null +++ b/test cases/unit/27 forcefallback/meson.build @@ -0,0 +1,8 @@ +project('mainproj', 'c', + default_options : ['wrap_mode=forcefallback']) + +zlib_dep = dependency('zlib', fallback: ['notzlib', 'zlib_dep']) + +test_not_zlib = executable('test_not_zlib', ['test_not_zlib.c'], dependencies: [zlib_dep]) + +test('test_not_zlib', test_not_zlib) diff --git a/test cases/unit/27 forcefallback/subprojects/notzlib/meson.build b/test cases/unit/27 forcefallback/subprojects/notzlib/meson.build new file mode 100644 index 0000000..254a136 --- /dev/null +++ b/test cases/unit/27 forcefallback/subprojects/notzlib/meson.build @@ -0,0 +1,7 @@ +project('notzlib', 'c') + +notzlib_sources = ['notzlib.c'] + +notzlib = library('notzlib', notzlib_sources) + +zlib_dep = declare_dependency(link_with: notzlib, include_directories: include_directories(['.'])) diff --git a/test cases/unit/27 forcefallback/subprojects/notzlib/notzlib.c b/test cases/unit/27 forcefallback/subprojects/notzlib/notzlib.c new file mode 100644 index 0000000..c3b6bf9 --- /dev/null +++ b/test cases/unit/27 forcefallback/subprojects/notzlib/notzlib.c @@ -0,0 +1,6 @@ +#include "notzlib.h" + +int not_a_zlib_function (void) +{ + return 42; +} diff --git a/test cases/unit/27 forcefallback/subprojects/notzlib/notzlib.h b/test cases/unit/27 forcefallback/subprojects/notzlib/notzlib.h new file mode 100644 index 0000000..695921d --- /dev/null +++ b/test cases/unit/27 forcefallback/subprojects/notzlib/notzlib.h @@ -0,0 +1,18 @@ +#pragma once + +#if defined _WIN32 || defined __CYGWIN__ +#if defined BUILDING_DLL + #define DLL_PUBLIC __declspec(dllexport) +#else + #define DLL_PUBLIC __declspec(dllimport) +#endif +#else + #if defined __GNUC__ + #define DLL_PUBLIC __attribute__ ((visibility("default"))) + #else + #pragma message ("Compiler does not support symbol visibility.") + #define DLL_PUBLIC + #endif +#endif + +int DLL_PUBLIC not_a_zlib_function (void); diff --git a/test cases/unit/27 forcefallback/test_not_zlib.c b/test cases/unit/27 forcefallback/test_not_zlib.c new file mode 100644 index 0000000..36256af --- /dev/null +++ b/test cases/unit/27 forcefallback/test_not_zlib.c @@ -0,0 +1,8 @@ +#include <notzlib.h> + +int main (int ac, char **av) +{ + if (not_a_zlib_function () != 42) + return 1; + return 0; +} diff --git a/test cases/unit/28 pkgconfig use libraries/app/app.c b/test cases/unit/28 pkgconfig use libraries/app/app.c new file mode 100644 index 0000000..b271a9e --- /dev/null +++ b/test cases/unit/28 pkgconfig use libraries/app/app.c @@ -0,0 +1,6 @@ +void libb_func(); + +int main() { + libb_func(); + return 0; +} diff --git a/test cases/unit/28 pkgconfig use libraries/app/meson.build b/test cases/unit/28 pkgconfig use libraries/app/meson.build new file mode 100644 index 0000000..3d85a32 --- /dev/null +++ b/test cases/unit/28 pkgconfig use libraries/app/meson.build @@ -0,0 +1,5 @@ +project('app', ['c']) + +b = dependency('test-b') + +executable('app', 'app.c', dependencies : [b]) diff --git a/test cases/unit/28 pkgconfig use libraries/lib/liba.c b/test cases/unit/28 pkgconfig use libraries/lib/liba.c new file mode 100644 index 0000000..e98906b --- /dev/null +++ b/test cases/unit/28 pkgconfig use libraries/lib/liba.c @@ -0,0 +1,2 @@ +void liba_func() { +} diff --git a/test cases/unit/28 pkgconfig use libraries/lib/libb.c b/test cases/unit/28 pkgconfig use libraries/lib/libb.c new file mode 100644 index 0000000..3160e5f --- /dev/null +++ b/test cases/unit/28 pkgconfig use libraries/lib/libb.c @@ -0,0 +1,5 @@ +void liba_func(); + +void libb_func() { + liba_func(); +} diff --git a/test cases/unit/28 pkgconfig use libraries/lib/meson.build b/test cases/unit/28 pkgconfig use libraries/lib/meson.build new file mode 100644 index 0000000..748adf4 --- /dev/null +++ b/test cases/unit/28 pkgconfig use libraries/lib/meson.build @@ -0,0 +1,16 @@ +project('lib', ['c']) + +a = library('test-a', 'liba.c', install: true) + +b = library('test-b', 'libb.c', link_with: a, install: true) + +import('pkgconfig').generate( + version: '0.0', + description: 'test library', + filebase: 'test-b', + name: 'test library', + libraries: [b], + subdirs: ['.'] +) + + diff --git a/test cases/unit/29 cross file overrides always args/meson.build b/test cases/unit/29 cross file overrides always args/meson.build new file mode 100644 index 0000000..ef6556e --- /dev/null +++ b/test cases/unit/29 cross file overrides always args/meson.build @@ -0,0 +1,3 @@ +project('cross compile args override always args', 'c') + +executable('no-file-offset-bits', 'test.c') diff --git a/test cases/unit/29 cross file overrides always args/test.c b/test cases/unit/29 cross file overrides always args/test.c new file mode 100644 index 0000000..315f92e --- /dev/null +++ b/test cases/unit/29 cross file overrides always args/test.c @@ -0,0 +1,8 @@ +#ifdef _FILE_OFFSET_BITS + #error "_FILE_OFFSET_BITS should not be set" +#endif + +int main(int argc, char *argv[]) +{ + return 0; +} diff --git a/test cases/unit/29 cross file overrides always args/ubuntu-armhf-overrides.txt b/test cases/unit/29 cross file overrides always args/ubuntu-armhf-overrides.txt new file mode 100644 index 0000000..a00a7d1 --- /dev/null +++ b/test cases/unit/29 cross file overrides always args/ubuntu-armhf-overrides.txt @@ -0,0 +1,19 @@ +[binaries] +# we could set exe_wrapper = qemu-arm-static but to test the case +# when cross compiled binaries can't be run we don't do that +c = '/usr/bin/arm-linux-gnueabihf-gcc' +cpp = '/usr/bin/arm-linux-gnueabihf-g++' +rust = ['rustc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7'] +ar = '/usr/arm-linux-gnueabihf/bin/ar' +strip = '/usr/arm-linux-gnueabihf/bin/strip' +pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config' + +[properties] +root = '/usr/arm-linux-gnueabihf' +c_args = ['-U_FILE_OFFSET_BITS'] + +[host_machine] +system = 'linux' +cpu_family = 'arm' +cpu = 'armv7' # Not sure if correct. +endian = 'little' |