diff options
86 files changed, 1275 insertions, 374 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 196ef0f..a1a9c5f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -52,6 +52,8 @@ platform: branches: only: - master + # Release branches + - /^[0-9]+\.[0-9]+$/ init: - ps: | @@ -62,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" ) ) @@ -92,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 48cfb38..16fa55b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ sudo: false branches: only: - master + # Release branches + - /^[0-9]+\.[0-9]+$/ os: - linux @@ -29,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/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/docs/markdown/Contributing.md b/docs/markdown/Contributing.md index 8e9847a..7b5fe73 100644 --- a/docs/markdown/Contributing.md +++ b/docs/markdown/Contributing.md @@ -18,6 +18,45 @@ Github](https://github.com/mesonbuild/meson/pulls). This causes them to be run through the CI system. All submissions must pass a full CI test run before they are even considered for submission. +## Acceptance and merging + +The kind of review and acceptance any merge proposal gets depends on +the changes it contains. All pull requests must be reviewed and +accepted by someone with commit rights who is not the original +submitter. Merge requests can be roughly split into three different +categories. + +The first one consists of MRs that only change the markdown +documentation under `docs/markdown`. Anyone with access rights can +push changes to these directly to master. For major changes it is +still recommended to create a MR so other people can comment on it. + +The second group consists of merges that don't change any +functionality, fixes to the CI system and bug fixes that have added +regression tests (see below) and don't change existing +functionality. Once successfully reviewed anyone with merge rights can +merge these to master. + +The final kind of merges are those that add new functionality or +change existing functionality in a backwards incompatible way. These +require the approval of the project lead. + +In a simplified list form the split would look like the following: + + - members with commit access can do: + - documentation changes (directly to master if warranted) + - bug fixes that don't change functionality + - refactorings + - new dependency types + - new tool support (e.g. a new Doxygen-kind of tool) + - support for new compilers to existing languages + - project leader decision is needed for: + - new modules + - new functions in the Meson language + - syntax changes for Meson files + - changes breaking backwards compatibility + - support for new languages + ## Tests All new features must come with automatic tests that thoroughly prove diff --git a/docs/markdown/Cross-compilation.md b/docs/markdown/Cross-compilation.md index 520a081..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 target 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 1dbc80a..f4cf89b 100644 --- a/docs/markdown/FAQ.md +++ b/docs/markdown/FAQ.md @@ -263,7 +263,7 @@ changed the value and from which value to which other value. Since people don't remember their own actions that far back, toggling between states based on long history would be confusing. -Because of this we do the simple an understandable thing: default +Because of this we do the simple and understandable thing: default values are only defaults and will never affect the value of an option once set. diff --git a/docs/markdown/Installing.md b/docs/markdown/Installing.md index 4670544..b8e6a81 100644 --- a/docs/markdown/Installing.md +++ b/docs/markdown/Installing.md @@ -29,6 +29,19 @@ install_man('foo.1') # -> share/man/man1/foo.1.gz install_data('datafile.dat', install_dir : join_paths(get_option('datadir'), 'progname')) # -> share/progname/datafile.dat ``` +`install_data()` supports rename of the file *since 0.46.0*. + +```meson +# file.txt -> {datadir}/{projectname}/new-name.txt +install_data('file.txt', rename : 'new-name.txt') + +# file1.txt -> share/myapp/dir1/data.txt +# file2.txt -> share/myapp/dir2/data.txt +install_data(['file1.txt', 'file2.txt'], + rename : ['dir1/data.txt', 'dir2/data.txt'], + install_dir : 'share/myapp') +``` + Sometimes you want to copy an entire subtree directly. For this use case there is the `install_subdir` command, which can be used like this. ```meson 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/Reference-manual.md b/docs/markdown/Reference-manual.md index 589baf1..5109b25 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -173,7 +173,8 @@ These are all the supported keyword arguments: mode, all the variables in the `configuration:` object (see above) are written to the `output:` file. - `install_dir` the subdirectory to install the generated file to - (e.g. `share/myproject`), if omitted the file is not installed. + (e.g. `share/myproject`), if omitted or given the value of empty + string, the file is not installed. - `output` the output file name (since v0.41.0, may contain `@PLAINNAME@` or `@BASENAME@` substitutions). In configuration mode, the permissions of the input file (if it is specified) are copied to @@ -368,9 +369,8 @@ can be of the following types: These input files can be sources, objects, libraries, or any other file. Meson will automatically categorize them based on the extension and use them accordingly. For instance, sources (`.c`, `.cpp`, -`.vala`, `.rs`, etc) will be compiled, objects (`.o`, `.obj`) and -libraries (`.so`, `.dll`, etc) will be linked, and all other files -(headers, unknown extensions, etc) will be ignored. +`.vala`, `.rs`, etc) will be compiled and objects (`.o`, `.obj`) and +libraries (`.so`, `.dll`, etc) will be linked. With the Ninja backend, Meson will create a build-time [order-only dependency](https://ninja-build.org/manual.html#ref_dependencies) on @@ -737,6 +737,13 @@ arguments. The following keyword arguments are supported: To leave any of these three as the default, specify `false`. +- `rename` if specified renames each source file into corresponding file + from `rename` list. Nested paths are allowed and they are joined with + `install_dir`. Length of `rename` list must be equal to the number of sources. + *(added 0.46.0)* + +See [Installing](Installing.md) for more examples. + ### install_headers() ``` meson @@ -1122,6 +1129,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 @@ -1159,9 +1193,17 @@ argument to [`dependency()`](#dependency). void test(name, executable, ...) ``` -Defines a unit test. Takes two positional arguments, the first is the -name of this test and the second is the executable to run. Keyword -arguments are the following. +Defines a test to run with the test harness. Takes two positional arguments, +the first is the name of the test and the second is the executable to run. +The executable can be an [executable build target object](#build-target-object) +returned by [`executable()`](#executable) or an +[external program object](#external-program-object) returned by +[`find_program()`](#find_program). The executable's exit code is used by the +test harness to record the outcome of the test, for example exit code zero +indicates success. For more on the Meson test harness protocol read +[Unit Tests](Unit-tests.md). + +Keyword arguments are the following: - `args` arguments to pass to the executable 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/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/howtox.md b/docs/markdown/howtox.md index 4e7e220..acc18d7 100644 --- a/docs/markdown/howtox.md +++ b/docs/markdown/howtox.md @@ -125,9 +125,9 @@ Install scan-build and configure your project. Then do this: $ ninja scan-build ``` -You can use the `SCAN_BUILD` environment variable to choose the scan-build executable. +You can use the `SCANBUILD` environment variable to choose the scan-build executable. ```console -$ SCAN_BUILD=<your exe> ninja scan-build +$ SCANBUILD=<your exe> ninja scan-build ``` diff --git a/docs/markdown/snippets/install_data-rename.md b/docs/markdown/snippets/install_data-rename.md new file mode 100644 index 0000000..6378d0f --- /dev/null +++ b/docs/markdown/snippets/install_data-rename.md @@ -0,0 +1,11 @@ +## install_data() supports rename + +`rename` parameter is used to change names of the installed files. +In order to install +- `file1.txt` into `share/myapp/dir1/data.txt` +- `file2.txt` into `share/myapp/dir2/data.txt` +```meson +install_data(['file1.txt', 'file2.txt'], + rename : ['dir1/data.txt', 'dir2/data.txt'], + install_dir : 'share/myapp') +``` 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/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b8ca71f..ad45204 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -89,12 +89,17 @@ class OptionProxy: class OptionOverrideProxy: '''Mimic an option list but transparently override selected option values.''' - def __init__(self, overrides, options): + def __init__(self, overrides, *options): self.overrides = overrides self.options = options def __getitem__(self, option_name): - base_opt = self.options[option_name] + for opts in self.options: + if option_name in opts: + return self._get_override(option_name, opts[option_name]) + raise KeyError('Option not found', option_name) + + def _get_override(self, option_name, base_opt): if option_name in self.overrides: return OptionProxy(base_opt.name, base_opt.validate_value(self.overrides[option_name])) return base_opt @@ -123,6 +128,20 @@ class Backend: def get_target_filename_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) + def get_builtin_options_for_target(self, target): + return OptionOverrideProxy(target.option_overrides, + self.environment.coredata.builtins) + + def get_base_options_for_target(self, target): + return OptionOverrideProxy(target.option_overrides, + self.environment.coredata.builtins, + self.environment.coredata.base_options) + + def get_compiler_options_for_target(self, target): + return OptionOverrideProxy(target.option_overrides, + # no code depends on builtins for now + self.environment.coredata.compiler_options) + def get_option_for_target(self, option_name, target): if option_name in target.option_overrides: override = target.option_overrides[option_name] @@ -444,7 +463,7 @@ class Backend: # starting from hard-coded defaults followed by build options and so on. commands = CompilerArgs(compiler) - copt_proxy = OptionOverrideProxy(target.option_overrides, self.environment.coredata.compiler_options) + copt_proxy = self.get_compiler_options_for_target(target) # First, the trivial ones that are impossible to override. # # Add -nostdinc/-nostdinc++ if needed; can't be overridden diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 4a180c7..cee1434 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -27,7 +27,7 @@ from .. import compilers from ..compilers import CompilerArgs from ..linkers import ArLinker from ..mesonlib import File, MesonException, OrderedSet -from ..mesonlib import get_compiler_for_source +from ..mesonlib import get_compiler_for_source, has_path_sep from .backends import CleanTrees, InstallData from ..build import InvalidArguments @@ -830,11 +830,10 @@ int dummy; subdir = de.install_dir if not subdir: subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name) - for f in de.sources: - assert(isinstance(f, mesonlib.File)) - plain_f = os.path.basename(f.fname) - dstabs = os.path.join(subdir, plain_f) - i = [f.absolute_path(srcdir, builddir), dstabs, de.install_mode] + for src_file, dst_name in zip(de.sources, de.rename): + assert(isinstance(src_file, mesonlib.File)) + dst_abs = os.path.join(subdir, dst_name) + i = [src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode] d.data.append(i) def generate_subdir_install(self, d): @@ -1287,7 +1286,7 @@ int dummy; # 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 '/' in target.name or '\\' in target.name: + if has_path_sep(target.name): # Target names really should not have slashes in them, but # unfortunately we did not check for that and some downstream projects # now have them. Once slashes are forbidden, remove this bit. @@ -2090,8 +2089,7 @@ rule FORTRAN_DEP_HACK return incs def _generate_single_compile(self, target, compiler, is_generated=False): - base_proxy = backends.OptionOverrideProxy(target.option_overrides, - self.environment.coredata.base_options) + base_proxy = self.get_base_options_for_target(target) # Create an empty commands list, and start adding arguments from # various sources in the order in which they must override each other commands = CompilerArgs(compiler) @@ -2123,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) @@ -2150,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 @@ -2243,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): @@ -2276,7 +2279,7 @@ rule FORTRAN_DEP_HACK # FIXME FIXME: The usage of this is a terrible and unreliable hack if isinstance(fname, File): return fname.subdir != '' - return '/' in fname or '\\' in fname + return has_path_sep(fname) # Fortran is a bit weird (again). When you link against a library, just compiling a source file # requires the mod files that are output when single files are built. To do this right we would need to @@ -2322,7 +2325,7 @@ rule FORTRAN_DEP_HACK pch = target.get_pch(lang) if not pch: continue - if '/' not in pch[0] or '/' not in pch[-1]: + if not has_path_sep(pch[0]) or not has_path_sep(pch[-1]): msg = 'Precompiled header of {!r} must not be in the same ' \ 'directory as source, please put it in a subdirectory.' \ ''.format(target.get_basename()) @@ -2499,7 +2502,7 @@ rule FORTRAN_DEP_HACK commands += linker.get_option_link_args(self.environment.coredata.compiler_options) # 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 '/' in target.name or '\\' in target.name: + if has_path_sep(target.name): # Target names really should not have slashes in them, but # unfortunately we did not check for that and some downstream projects # now have them. Once slashes are forbidden, remove this bit. diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 3b0dc0e..5e972f2 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -731,7 +731,7 @@ class Vs2010Backend(backends.Backend): # generate_single_compile() and generate_basic_compiler_args() for l, comp in target.compilers.items(): if l in file_args: - file_args[l] += compilers.get_base_compile_args(self.environment.coredata.base_options, comp) + file_args[l] += compilers.get_base_compile_args(self.get_base_options_for_target(target), comp) file_args[l] += comp.get_option_compile_args(self.environment.coredata.compiler_options) # Add compile args added using add_project_arguments() for l, args in self.build.projects_args.get(target.subproject, {}).items(): @@ -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 diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 5c9f346..aca592e 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -23,7 +23,7 @@ from . import mlog from .mesonlib import File, MesonException, listify, extract_as_list from .mesonlib import typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values -from .mesonlib import for_windows, for_darwin, for_cygwin, for_android +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, @@ -85,6 +85,8 @@ 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'}) class InvalidArguments(MesonException): pass @@ -286,7 +288,7 @@ class EnvironmentVariables: class Target: def __init__(self, name, subdir, subproject, build_by_default): - if '/' in name or '\\' in name: + if has_path_sep(name): # Fix failing test 53 when this becomes an error. mlog.warning('''Target "%s" has a path separator in its name. This is not supported, it can cause unexpected failures and will become @@ -378,6 +380,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() @@ -1025,6 +1028,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): @@ -1067,7 +1079,7 @@ class Generator: raise InvalidArguments('"output" may only contain strings.') if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.') - if '/' in rule or '\\' in rule: + if has_path_sep(rule): raise InvalidArguments('"outputs" must not contain a directory separator.') if len(outputs) > 1: for o in outputs: @@ -1666,7 +1678,7 @@ class CustomTarget(Target): raise InvalidArguments('Output must not be empty.') if i.strip() == '': raise InvalidArguments('Output must not consist only of whitespace.') - if '/' in i: + if has_path_sep(i): raise InvalidArguments('Output must not contain a path segment.') if '@INPUT@' in i or '@INPUT0@' in i: m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \ @@ -1824,6 +1836,8 @@ 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: @@ -1894,13 +1908,19 @@ class ConfigurationData: # A bit poorly named, but this represents plain data files to copy # during install. class Data: - def __init__(self, sources, install_dir, install_mode=None): + def __init__(self, sources, install_dir, install_mode=None, rename=None): self.sources = sources self.install_dir = install_dir self.install_mode = install_mode self.sources = listify(self.sources) for s in self.sources: assert(isinstance(s, File)) + if rename is None: + self.rename = [os.path.basename(f.fname) for f in self.sources] + else: + self.rename = stringlistify(rename) + if len(self.rename) != len(self.sources): + raise MesonException('Size of rename argument is different from number of sources') class RunScript(dict): def __init__(self, script, args): diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 2d14116..56b46b4 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -416,7 +416,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, 1, 1024, None, prefix, env, extra_args, dependencies) def sizeof(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: @@ -936,7 +936,7 @@ class VisualStudioCCompiler(CCompiler): self.warn_args = {'1': ['/W2'], '2': ['/W3'], '3': ['/W4']} - self.base_options = ['b_pch'] # FIXME add lto, pgo and the like + self.base_options = ['b_pch', 'b_ndebug'] # FIXME add lto, pgo and the like self.is_64 = is_64 # Override CCompiler.get_always_args diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 034fef4..a28a225 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -315,7 +315,9 @@ def get_base_compile_args(options, compiler): except KeyError: pass try: - if options['b_ndebug'].value == 'true' or (options['b_ndebug'].value == 'if-release' and options['buildtype'] == 'release'): + if (options['b_ndebug'].value == 'true' or + (options['b_ndebug'].value == 'if-release' and + options['buildtype'].value == 'release')): args += ['-DNDEBUG'] except KeyError: pass @@ -348,7 +350,7 @@ def get_base_link_args(options, linker, is_shared_module): pass try: if 'b_asneeded' in linker.base_options and options['b_asneeded'].value: - args.append('-Wl,--as-needed') + args.append(linker.get_asneeded_args()) except KeyError: pass try: @@ -900,6 +902,13 @@ ICC_STANDARD = 0 ICC_OSX = 1 ICC_WIN = 2 +# GNU ld cannot be installed on macOS +# https://github.com/Homebrew/homebrew-core/issues/17794#issuecomment-328174395 +# Hence, we don't need to differentiate between OS and ld +# for the sake of adding as-needed support +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): if soversion is None: sostr = '' @@ -1002,10 +1011,18 @@ class GnuCompiler: 'b_colorout', 'b_ndebug', 'b_staticpic'] if self.gcc_type != GCC_OSX: self.base_options.append('b_lundef') - self.base_options.append('b_asneeded') + self.base_options.append('b_asneeded') # All GCC backends can do assembly self.can_compile_suffixes.add('s') + # TODO: centralise this policy more globally, instead + # of fragmenting it into GnuCompiler and ClangCompiler + def get_asneeded_args(self): + if self.gcc_type == GCC_OSX: + return APPLE_LD_AS_NEEDED + else: + return GNU_LD_AS_NEEDED + def get_colorout_args(self, colortype): if mesonlib.version_compare(self.version, '>=4.9.0'): return gnu_color_args[colortype][:] @@ -1084,10 +1101,18 @@ class ClangCompiler: 'b_ndebug', 'b_staticpic', 'b_colorout'] if self.clang_type != CLANG_OSX: self.base_options.append('b_lundef') - self.base_options.append('b_asneeded') + self.base_options.append('b_asneeded') # All Clang backends can do assembly and LLVM IR self.can_compile_suffixes.update(['ll', 's']) + # TODO: centralise this policy more globally, instead + # of fragmenting it into GnuCompiler and ClangCompiler + def get_asneeded_args(self): + if self.clang_type == CLANG_OSX: + return APPLE_LD_AS_NEEDED + else: + return GNU_LD_AS_NEEDED + def get_pic_args(self): if self.clang_type in (CLANG_WIN, CLANG_OSX): return [] # On Window and OS X, pic is always on. @@ -1207,6 +1232,14 @@ class IntelCompiler: 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) + # TODO: centralise this policy more globally, instead + # of fragmenting it into GnuCompiler and ClangCompiler + def get_asneeded_args(self): + if self.icc_type == CLANG_OSX: + return APPLE_LD_AS_NEEDED + else: + return GNU_LD_AS_NEEDED + def get_std_shared_lib_link_args(self): # FIXME: Don't know how icc works on OSX # if self.icc_type == ICC_OSX: diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index ff7c706..0115fb3 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -674,7 +674,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 cab8bf3..55320d0 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -21,12 +21,12 @@ from . import optinterpreter from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, Popen_safe, listify, extract_as_list +from .mesonlib import FileMode, Popen_safe, listify, extract_as_list, has_path_sep 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 @@ -1500,7 +1500,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'find_program': {'required', 'native'}, 'generator': {'arguments', 'output', 'depfile', 'capture', 'preserve_path_from'}, 'include_directories': {'is_system'}, - 'install_data': {'install_dir', 'install_mode', 'sources'}, + 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, 'install_headers': {'install_dir', 'subdir'}, 'install_man': {'install_dir'}, 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, @@ -1612,6 +1612,7 @@ class Interpreter(InterpreterBase): 'static_library': self.func_static_lib, 'test': self.func_test, 'vcs_tag': self.func_vcs_tag, + 'subdir_done': self.func_subdir_done, }) if 'MESON_UNIT_TEST' in os.environ: self.funcs.update({'exception': self.func_exception}) @@ -1863,7 +1864,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Subproject name must not contain a ".." path segment.') if os.path.isabs(dirname): raise InterpreterException('Subproject name must not be an absolute path.') - if '\\' in dirname or '/' in dirname: + if has_path_sep(dirname): mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.') if dirname in self.subproject_stack: fullstack = self.subproject_stack + [dirname] @@ -2310,7 +2311,10 @@ to directly access options of other subprojects.''') return progobj 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 @@ -2349,7 +2353,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 @@ -2607,6 +2611,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: @@ -2826,7 +2838,8 @@ root and issuing %s. if not isinstance(install_dir, (str, type(None))): raise InvalidArguments('Keyword argument install_dir not a string.') install_mode = self._get_kwarg_install_mode(kwargs) - data = DataHolder(build.Data(sources, install_dir, install_mode)) + rename = kwargs.get('rename', None) + data = DataHolder(build.Data(sources, install_dir, install_mode, rename)) self.build.data.append(data.held_object) return data @@ -2972,9 +2985,11 @@ root and issuing %s. conffile = os.path.normpath(inputfile.relative_name()) if conffile not in self.build_def_files: self.build_def_files.append(conffile) - # Install file if requested + # Install file if requested, we check for the empty string + # for backwards compatibility. That was the behaviour before + # 0.45.0 so preserve it. idir = kwargs.get('install_dir', None) - if isinstance(idir, str): + if isinstance(idir, str) and idir: cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname) self.build.data.append(build.Data([cfile], idir)) return mesonlib.File.from_built_file(self.subdir, output) 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/mconf.py b/mesonbuild/mconf.py index b409615..cadd306 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -17,13 +17,15 @@ import sys import argparse from . import (coredata, mesonlib, build) -parser = argparse.ArgumentParser(prog='meson configure') +def buildparser(): + parser = argparse.ArgumentParser(prog='meson configure') -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 class ConfException(mesonlib.MesonException): @@ -226,7 +228,7 @@ def run(args): args = mesonlib.expand_arguments(args) if not args: args = [os.getcwd()] - options = parser.parse_args(args) + options = buildparser().parse_args(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 4e72600..a076e3e 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -21,6 +21,21 @@ import platform, subprocess, operator, os, shutil, re import collections from mesonbuild import mlog +have_fcntl = False +have_msvcrt = False + +try: + import fcntl + have_fcntl = True +except Exception: + pass + +try: + import msvcrt + have_msvcrt = True +except Exception: + pass + from glob import glob def detect_meson_py_location(): @@ -519,6 +534,12 @@ def get_library_dirs(): unixdirs += glob('/lib/' + plat + '*') return unixdirs +def has_path_sep(name, sep='/\\'): + 'Checks if any of the specified @sep path separators are in @name' + for each in sep: + if each in name: + return True + return False def do_replacement(regex, line, confdata): missing_variables = set() @@ -972,3 +993,26 @@ class OrderedSet(collections.MutableSet): def difference(self, set_): return type(self)(e for e in self if e not in set_) + +class BuildDirLock: + + def __init__(self, builddir): + self.lockfilename = os.path.join(builddir, 'meson-private/meson.lock') + + def __enter__(self): + self.lockfile = open(self.lockfilename, 'w') + try: + if have_fcntl: + fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) + elif have_msvcrt: + msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1) + except (BlockingIOError, PermissionError): + self.lockfile.close() + raise MesonException('Some other Meson process is already using this build directory. Exiting.') + + def __exit__(self, *args): + if have_fcntl: + fcntl.flock(self.lockfile, fcntl.LOCK_UN) + elif have_msvcrt: + msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1) + self.lockfile.close() diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 9c4498c..651224e 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -147,7 +147,8 @@ class MesonApp: def generate(self): env = environment.Environment(self.source_dir, self.build_dir, self.meson_script_launcher, self.options, self.original_cmd_line_args) mlog.initialize(env.get_log_dir()) - self._generate(env) + with mesonlib.BuildDirLock(self.build_dir): + self._generate(env) def _generate(self, env): mlog.debug('Build started at', datetime.datetime.now().isoformat()) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 23e666c..74d26da 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='?', 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/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 074fc5a..ef74d63 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: @@ -83,12 +89,15 @@ class DependenciesHelper: for obj in libs: if 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 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,7 +105,20 @@ class DependenciesHelper: if obj.found(): processed_libs += obj.get_link_args() processed_cflags += obj.get_compile_args() - elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)): + elif isinstance(obj, build.SharedLibrary): + 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. processed_libs.append(obj) if public: if not hasattr(obj, 'generated_pc'): @@ -110,12 +132,49 @@ class DependenciesHelper: return processed_libs, processed_reqs, processed_cflags + def add_version_reqs(self, name, version_reqs): + if version_reqs: + vreqs = self.version_reqs.get(name, []) + vreqs += mesonlib.stringlistify(version_reqs) + self.version_reqs[name] = 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] @@ -187,11 +246,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))) @@ -241,20 +301,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', '') @@ -263,6 +337,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', [])) @@ -273,7 +349,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'] @@ -302,7 +378,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): diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index fbd6e8b..91567f2 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 + 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: - 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 - 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.') @@ -509,13 +536,14 @@ TIMEOUT: %4d self.logfilename = logfile_base + '.txt' self.jsonlogfilename = logfile_base + '.json' - self.jsonlogfile = open(self.jsonlogfilename, 'w') - self.logfile = open(self.logfilename, 'w') + self.jsonlogfile = open(self.jsonlogfilename, 'w', encoding='utf-8') + self.logfile = open(self.logfilename, 'w', encoding='utf-8') 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 +584,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 +615,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 +636,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 +648,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 +677,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/optinterpreter.py b/mesonbuild/optinterpreter.py index 16eaf78..b4156ff 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -78,7 +78,7 @@ def BooleanParser(name, description, kwargs): kwargs.get('value', True), kwargs.get('yield', coredata.default_yielding)) -@permitted_kwargs({'value', 'yiel', 'choices'}) +@permitted_kwargs({'value', 'yield', 'choices'}) def ComboParser(name, description, kwargs): if 'choices' not in kwargs: raise OptionException('Combo option missing "choices" keyword.') 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/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/yelphelper.py b/mesonbuild/scripts/yelphelper.py index ab99267..0f8b0b8 100644 --- a/mesonbuild/scripts/yelphelper.py +++ b/mesonbuild/scripts/yelphelper.py @@ -17,6 +17,7 @@ import subprocess import shutil import argparse from .. import mlog +from ..mesonlib import has_path_sep from . import destdir_join from .gettext import read_linguas @@ -79,7 +80,7 @@ def install_help(srcdir, blddir, sources, media, langs, install_dir, destdir, pr elif symlinks: srcfile = os.path.join(c_install_dir, m) mlog.log('Symlinking %s to %s.' % (outfile, srcfile)) - if '/' in m or '\\' in m: + if has_path_sep(m): os.makedirs(os.path.dirname(outfile), exist_ok=True) try: try: @@ -94,7 +95,7 @@ def install_help(srcdir, blddir, sources, media, langs, install_dir, destdir, pr # Lang doesn't have media file so copy it over 'C' one infile = os.path.join(srcdir, 'C', m) mlog.log('Installing %s to %s' % (infile, outfile)) - if '/' in m or '\\' in m: + if has_path_sep(m): os.makedirs(os.path.dirname(outfile), exist_ok=True) shutil.copyfile(infile, outfile) shutil.copystat(infile, outfile) 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 6e9970e..9f0ae3f 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -37,6 +37,7 @@ from mesonbuild.interpreter import ObjectHolder from mesonbuild.mesonlib import ( is_windows, is_osx, is_cygwin, is_dragonflybsd, windows_proof_rmtree, python_command, meson_command, version_compare, + BuildDirLock ) from mesonbuild.environment import Environment, detect_ninja from mesonbuild.mesonlib import MesonException, EnvironmentException @@ -438,10 +439,11 @@ 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) @@ -513,7 +515,8 @@ class BasePlatformTests(unittest.TestCase): windows_proof_rmtree(path) except FileNotFoundError: pass - os.environ = self.orig_env + os.environ.clear() + os.environ.update(self.orig_env) super().tearDown() def _run(self, command, workdir=None): @@ -521,16 +524,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) @@ -588,10 +593,11 @@ class BasePlatformTests(unittest.TestCase): def run_tests(self): self._run(self.test_command, workdir=self.builddir) - def install(self): + def install(self, *, use_destdir=True): if self.backend is not Backend.ninja: raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) - os.environ['DESTDIR'] = self.installdir + if use_destdir: + os.environ['DESTDIR'] = self.installdir self._run(self.install_command, workdir=self.builddir) def uninstall(self): @@ -1808,7 +1814,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 @@ -1880,6 +1887,42 @@ int main(int argc, char **argv) { self.init(testdir, extra_args=['--layout=flat']) self.build() + def test_flock(self): + exception_raised = False + with tempfile.TemporaryDirectory() as tdir: + os.mkdir(os.path.join(tdir, 'meson-private')) + with BuildDirLock(tdir): + try: + with BuildDirLock(tdir): + pass + except MesonException: + 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']) + self.build() + exe = os.path.join(self.builddir, 'main') + self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip()) + + def test_ndebug_if_release_enabled(self): + testdir = os.path.join(self.unit_test_dir, '25 ndebug if-release') + self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release']) + self.build() + exe = os.path.join(self.builddir, 'main') + self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip()) + class FailureTests(BasePlatformTests): ''' @@ -2237,11 +2280,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'])) @@ -2257,17 +2300,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): @@ -2735,6 +2779,42 @@ endian = 'little' self.build() mesonbuild.modules.gnome.native_glib_version = None + @unittest.skipIf(shutil.which('pkg-config') is None, 'Pkg-config not found.') + def test_pkgconfig_usage(self): + testdir1 = os.path.join(self.unit_test_dir, '24 pkgconfig usage/dependency') + testdir2 = os.path.join(self.unit_test_dir, '24 pkgconfig usage/dependee') + if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) != 0: + raise unittest.SkipTest('Glib 2.0 dependency not available.') + with tempfile.TemporaryDirectory() as tempdirname: + self.init(testdir1, ['--prefix=' + tempdirname, '--libdir=lib'], default_args=False) + self.install(use_destdir=False) + shutil.rmtree(self.builddir) + os.mkdir(self.builddir) + pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') + self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc'))) + lib_dir = os.path.join(tempdirname, 'lib') + os.environ['PKG_CONFIG_PATH'] = pkg_dir + # Private internal libraries must not leak out. + pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep']) + self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.') + # Dependencies must not leak to cflags when building only a shared library. + pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep']) + self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.') + # Test that the result is usable. + self.init(testdir2) + self.build() + myenv = os.environ.copy() + myenv['LD_LIBRARY_PATH'] = lib_dir + if is_cygwin(): + bin_dir = os.path.join(tempdirname, 'bin') + myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH'] + self.assertTrue(os.path.isdir(lib_dir)) + test_exe = os.path.join(self.builddir, 'pkguser') + self.assertTrue(os.path.isfile(test_exe)) + subprocess.check_call(test_exe, env=myenv) + class LinuxArmCrossCompileTests(BasePlatformTests): ''' 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/12 data/installed_files.txt b/test cases/common/12 data/installed_files.txt index ab1a981..43bb0e5 100644 --- a/test cases/common/12 data/installed_files.txt +++ b/test cases/common/12 data/installed_files.txt @@ -2,6 +2,10 @@ usr/share/progname/datafile.dat usr/share/progname/fileobject_datafile.dat usr/share/progname/vanishing.dat usr/share/progname/vanishing2.dat +usr/share/data install test/renamed file.txt usr/share/data install test/somefile.txt +usr/share/data install test/some/nested/path.txt +usr/share/renamed/renamed 2.txt +usr/share/renamed/renamed 3.txt etc/etcfile.dat usr/bin/runscript.sh diff --git a/test cases/common/12 data/meson.build b/test cases/common/12 data/meson.build index 4528afe..d855bba 100644 --- a/test cases/common/12 data/meson.build +++ b/test cases/common/12 data/meson.build @@ -15,3 +15,9 @@ install_data(files('somefile.txt')) subdir('vanishing') install_data(sources : 'vanishing/vanishing2.dat', install_dir : 'share/progname') + +install_data(sources : 'to_be_renamed_1.txt', rename : 'renamed file.txt') +install_data(sources : ['vanishing/to_be_renamed_2.txt', 'to_be_renamed_3.txt'], + install_dir : 'share/renamed', + rename : ['renamed 2.txt', 'renamed 3.txt']) +install_data(sources : 'to_be_renamed_4.txt', rename : 'some/nested/path.txt') diff --git a/test cases/common/12 data/to_be_renamed_1.txt b/test cases/common/12 data/to_be_renamed_1.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/12 data/to_be_renamed_1.txt diff --git a/test cases/common/12 data/to_be_renamed_3.txt b/test cases/common/12 data/to_be_renamed_3.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/12 data/to_be_renamed_3.txt diff --git a/test cases/common/12 data/to_be_renamed_4.txt b/test cases/common/12 data/to_be_renamed_4.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/12 data/to_be_renamed_4.txt diff --git a/test cases/common/12 data/vanishing/to_be_renamed_2.txt b/test cases/common/12 data/vanishing/to_be_renamed_2.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/12 data/vanishing/to_be_renamed_2.txt 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/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index eda0a8f..71a2563 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -131,3 +131,9 @@ configure_file( configuration : conf6 ) test('test6', executable('prog6', 'prog6.c')) + +# test empty install dir string +cfile = configure_file(input : 'config.h.in', + output : 'do_not_get_installed.h', + install_dir : '', + configuration : conf) diff --git a/test cases/common/184 as-needed/config.h b/test cases/common/184 as-needed/config.h new file mode 100644 index 0000000..b8fb60f --- /dev/null +++ b/test cases/common/184 as-needed/config.h @@ -0,0 +1,14 @@ +#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 diff --git a/test cases/common/184 as-needed/libA.cpp b/test cases/common/184 as-needed/libA.cpp new file mode 100644 index 0000000..5f45bc0 --- /dev/null +++ b/test cases/common/184 as-needed/libA.cpp @@ -0,0 +1,7 @@ +#define BUILDING_DLL + +#include "libA.h" + +namespace meson_test_as_needed { + DLL_PUBLIC bool linked = false; +} diff --git a/test cases/common/184 as-needed/libA.h b/test cases/common/184 as-needed/libA.h new file mode 100644 index 0000000..8e76d22 --- /dev/null +++ b/test cases/common/184 as-needed/libA.h @@ -0,0 +1,5 @@ +#include "config.h" + +namespace meson_test_as_needed { + DLL_PUBLIC extern bool linked; +} diff --git a/test cases/common/184 as-needed/libB.cpp b/test cases/common/184 as-needed/libB.cpp new file mode 100644 index 0000000..a872394 --- /dev/null +++ b/test cases/common/184 as-needed/libB.cpp @@ -0,0 +1,19 @@ +#include "libA.h" + +#undef DLL_PUBLIC +#define BUILDING_DLL +#include "config.h" + +namespace meson_test_as_needed { + namespace { + bool set_linked() { + linked = true; + return true; + } + bool stub = set_linked(); + } + + DLL_PUBLIC int libB_unused_func() { + return 0; + } +} diff --git a/test cases/common/184 as-needed/main.cpp b/test cases/common/184 as-needed/main.cpp new file mode 100644 index 0000000..191d15c --- /dev/null +++ b/test cases/common/184 as-needed/main.cpp @@ -0,0 +1,7 @@ +#include <cstdlib> + +#include "libA.h" + +int main() { + return !meson_test_as_needed::linked ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test cases/common/184 as-needed/meson.build b/test cases/common/184 as-needed/meson.build new file mode 100644 index 0000000..3b54aaa --- /dev/null +++ b/test cases/common/184 as-needed/meson.build @@ -0,0 +1,13 @@ +project('as-needed test', 'cpp') + +# Idea behind this test is to have -Wl,--as-needed prune +# away unneeded linkages, which would otherwise cause global +# static initialiser side-effects to set a boolean to true. + +# Credits for portable ISO C++ idea go to sarum9in + +libA = library('A', 'libA.cpp') +libB = library('B', 'libB.cpp', link_with : libA) + +main_exe = executable('C', 'main.cpp', link_with : [libA, libB]) +test('main test', main_exe) diff --git a/test cases/common/185 ndebug if-release enabled/main.c b/test cases/common/185 ndebug if-release enabled/main.c new file mode 100644 index 0000000..984ebca --- /dev/null +++ b/test cases/common/185 ndebug if-release enabled/main.c @@ -0,0 +1,15 @@ +#include <assert.h> +#include <stdlib.h> + +int meson_test_side_effect = EXIT_FAILURE; + +int meson_test_set_side_effect(void) { + meson_test_side_effect = EXIT_SUCCESS; + return 1; +} + +int main(void) { + // meson_test_side_effect is set only if assert is executed + assert(meson_test_set_side_effect()); + return meson_test_side_effect; +} diff --git a/test cases/common/185 ndebug if-release enabled/meson.build b/test cases/common/185 ndebug if-release enabled/meson.build new file mode 100644 index 0000000..be26375 --- /dev/null +++ b/test cases/common/185 ndebug if-release enabled/meson.build @@ -0,0 +1,7 @@ +project('ndebug enabled', 'c', + default_options : [ + 'buildtype=debugoptimized', + 'b_ndebug=if-release', + ]) + +test('exe', executable('main', 'main.c')) diff --git a/test cases/common/186 ndebug if-release disabled/main.c b/test cases/common/186 ndebug if-release disabled/main.c new file mode 100644 index 0000000..cb3ec3f --- /dev/null +++ b/test cases/common/186 ndebug if-release disabled/main.c @@ -0,0 +1,7 @@ +#include <assert.h> +#include <stdlib.h> + +int main(void) { + assert(0); + return EXIT_SUCCESS; +} diff --git a/test cases/common/186 ndebug if-release disabled/meson.build b/test cases/common/186 ndebug if-release disabled/meson.build new file mode 100644 index 0000000..a9a79ea --- /dev/null +++ b/test cases/common/186 ndebug if-release disabled/meson.build @@ -0,0 +1,7 @@ +project('ndebug disabled', 'c', + default_options : [ + 'buildtype=release', + 'b_ndebug=if-release', + ]) + +test('exe', executable('main', 'main.c')) 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/51 pkgconfig-gen/dependencies/meson.build b/test cases/common/51 pkgconfig-gen/dependencies/meson.build index 018b72f..d13f009 100644 --- a/test cases/common/51 pkgconfig-gen/dependencies/meson.build +++ b/test cases/common/51 pkgconfig-gen/dependencies/meson.build @@ -1,20 +1,16 @@ -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 = shared_library('libmain', link_with : [exposed_lib, internal_lib]) +main_lib = static_library('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') notfound_dep = dependency('notfound', required : false) threads_dep = dependency('threads') custom_dep = declare_dependency(link_args : ['-lcustom'], compile_args : ['-DCUSTOM']) 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/70 install_data rename bad size/file1.txt b/test cases/failing/70 install_data rename bad size/file1.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/failing/70 install_data rename bad size/file1.txt diff --git a/test cases/failing/70 install_data rename bad size/file2.txt b/test cases/failing/70 install_data rename bad size/file2.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/failing/70 install_data rename bad size/file2.txt diff --git a/test cases/failing/70 install_data rename bad size/meson.build b/test cases/failing/70 install_data rename bad size/meson.build new file mode 100644 index 0000000..c7cde08 --- /dev/null +++ b/test cases/failing/70 install_data rename bad size/meson.build @@ -0,0 +1,3 @@ +project('data install test', 'c') + +install_data(['file1.txt', 'file2.txt'], rename : 'just one name') 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/unit/24 pkgconfig usage/dependee/meson.build b/test cases/unit/24 pkgconfig usage/dependee/meson.build new file mode 100644 index 0000000..beb446c --- /dev/null +++ b/test cases/unit/24 pkgconfig usage/dependee/meson.build @@ -0,0 +1,7 @@ +project('pkgconfig user', 'c') + +pkgdep = dependency('libpkgdep') + +executable('pkguser', 'pkguser.c', + dependencies : pkgdep) + diff --git a/test cases/unit/24 pkgconfig usage/dependee/pkguser.c b/test cases/unit/24 pkgconfig usage/dependee/pkguser.c new file mode 100644 index 0000000..2bff316 --- /dev/null +++ b/test cases/unit/24 pkgconfig usage/dependee/pkguser.c @@ -0,0 +1,6 @@ +#include<pkgdep.h> + +int main(int argc, char **argv) { + int res = pkgdep(); + return res != 99; +} diff --git a/test cases/unit/24 pkgconfig usage/dependency/meson.build b/test cases/unit/24 pkgconfig usage/dependency/meson.build new file mode 100644 index 0000000..89fae8e --- /dev/null +++ b/test cases/unit/24 pkgconfig usage/dependency/meson.build @@ -0,0 +1,24 @@ +project('pkgconfig dep', 'c', + version : '1.0.0') + +# This is not used in the code, only to check that it does not +# leak out to --libs. +glib_dep = dependency('glib-2.0') + +pkgconfig = import('pkgconfig') + +intlib = static_library('libpkgdep-int', 'privatelib.c') +intdep = declare_dependency(link_with : intlib) + +lib = shared_library('pkgdep', 'pkgdep.c', + dependencies : [glib_dep, intdep], + install : true) + +install_headers('pkgdep.h') + +pkgconfig.generate( + filebase : 'libpkgdep', + name : 'Libpkgdep', + description : 'Sample pkgconfig dependency library', + version : meson.project_version(), + libraries : lib) diff --git a/test cases/unit/24 pkgconfig usage/dependency/pkgdep.c b/test cases/unit/24 pkgconfig usage/dependency/pkgdep.c new file mode 100644 index 0000000..bd5c3f4 --- /dev/null +++ b/test cases/unit/24 pkgconfig usage/dependency/pkgdep.c @@ -0,0 +1,7 @@ +#include<pkgdep.h> + +int internal_thingy(); + +int pkgdep() { + return internal_thingy(); +} diff --git a/test cases/unit/24 pkgconfig usage/dependency/pkgdep.h b/test cases/unit/24 pkgconfig usage/dependency/pkgdep.h new file mode 100644 index 0000000..16d622e --- /dev/null +++ b/test cases/unit/24 pkgconfig usage/dependency/pkgdep.h @@ -0,0 +1,3 @@ +#pragma once + +int pkgdep(); diff --git a/test cases/unit/24 pkgconfig usage/dependency/privatelib.c b/test cases/unit/24 pkgconfig usage/dependency/privatelib.c new file mode 100644 index 0000000..71d2179 --- /dev/null +++ b/test cases/unit/24 pkgconfig usage/dependency/privatelib.c @@ -0,0 +1,3 @@ +int internal_thingy() { + return 99; +} diff --git a/test cases/unit/25 ndebug if-release/main.c b/test cases/unit/25 ndebug if-release/main.c new file mode 100644 index 0000000..70b3d04 --- /dev/null +++ b/test cases/unit/25 ndebug if-release/main.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <stdlib.h> + +int main(void) { +#ifdef NDEBUG + printf("NDEBUG=1\n"); +#else + printf("NDEBUG=0\n"); +#endif + return 0; +} diff --git a/test cases/unit/25 ndebug if-release/meson.build b/test cases/unit/25 ndebug if-release/meson.build new file mode 100644 index 0000000..4af2406 --- /dev/null +++ b/test cases/unit/25 ndebug if-release/meson.build @@ -0,0 +1,3 @@ +project('ndebug enabled', 'c') + +executable('main', 'main.c') 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 |