diff options
104 files changed, 1622 insertions, 524 deletions
diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..10483d3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,9 @@ +[MASTER] +jobs=0 + +[REPORTS] +score=no + +[MESSAGES CONTROL] +disable=all +enable=unreachable
\ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8f393f1..cd78f41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ script: - | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ci_env=`bash <(curl -s https://codecov.io/env)` - docker run $ci_env -v ${PWD}/.coverage:/root/.coverage \ + docker run --security-opt seccomp:unconfined $ci_env -v ${PWD}/.coverage:/root/.coverage \ withgit \ /bin/sh -c "cd /root && mkdir -p tools; wget -c http://nirbheek.in/files/binaries/ninja/linux-amd64/ninja -O /root/tools/ninja; chmod +x /root/tools/ninja; CC=$CC CXX=$CXX OBJC=$CC OBJCXX=$CXX PATH=/root/tools:$PATH MESON_FIXED_NINJA=1 ./run_tests.py $RUN_TESTS_ARGS -- $MESON_ARGS && chmod -R a+rwX .coverage" fi diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5ec62f1..56dc4b9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,6 +12,21 @@ variables: CI: 1 jobs: +- job: Pylint + pool: + vmImage: ubuntu-latest + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.7' + addToPath: true + - script: python -m pip install pylint + displayName: Install Pylint + - script: pylint mesonbuild + displayName: Lint Checks + + - job: vs2015 pool: vmImage: vs2015-win2012r2 @@ -88,7 +103,6 @@ jobs: gccx64ninja: {} variables: CYGWIN_ROOT: $(System.Workfolder)\cygwin - CYGWIN_CMAKE_LINK: http://cygwin.mirror.constant.com/x86_64/release/cmake/cmake-3.13.1-1.tar.xz CYGWIN_MIRROR: http://cygwin.mirror.constant.com steps: - script: | @@ -112,15 +126,16 @@ jobs: python35-pip,^ vala,^ wget,^ + cmake,^ zlib-devel displayName: Install Dependencies - script: | - %CYGWIN_ROOT%\bin\bash.exe -cl "wget %CYGWIN_CMAKE_LINK% -O cmake.tar.xz" - %CYGWIN_ROOT%\bin\bash.exe -cl "tar -xf cmake.tar.xz -C /" - displayName: Manually install CMake 3.13.1 + %CYGWIN_ROOT%\bin\python3.5m.exe -m pip --disable-pip-version-check install pytest-xdist + displayName: pip install pytest-xdist - script: | set BOOST_ROOT= set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32 + # FIXME: we need to support systems without unversioned `python3` cp /usr/bin/python3.5 /usr/bin/python3 env.exe -- python3 run_tests.py --backend=ninja displayName: Run Tests diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml index 36e6fb4..d2e984f 100644 --- a/ci/azure-steps.yml +++ b/ci/azure-steps.yml @@ -155,7 +155,7 @@ steps: python --version # Needed for running unit tests in parallel. - python -m pip install --upgrade pytest-xdist + python -m pip --disable-pip-version-check install --upgrade pytest-xdist echo "" diff --git a/cross/armclang-linux.txt b/cross/armclang-linux.txt new file mode 100644 index 0000000..2772d82 --- /dev/null +++ b/cross/armclang-linux.txt @@ -0,0 +1,35 @@ +# Using ARM compilers from Linux command line is tricky and +# not really well documented because they want you to use +# their IDE instead. +# +# First you need to do the full install with the IDE and set +# up license files et al. This may be possible from the command +# line. +# +# Then you need to do the following: +# +# Select toolchain by running /opt/arm/developmentstudio-2019.0/bin/select_default_toolchain +# Armcc is only available in toolchain version 5. +# Armclang is only available in toolchain version 6. +# Start shell with /opt/arm/developmentstudio-2019.0/bin/suite_exec zsh +# Now the compilers will work. + +[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 = '/opt/arm/developmentstudio-2019.0/sw/ARMCompiler6.12/bin/armclang' +#c = '/opt/arm/developmentstudio-2019.0/sw/ARMCompiler5.06u6/bin/armcc' +#cpp = '/usr/bin/arm-linux-gnueabihf-g++' +ar = '/opt/arm/developmentstudio-2019.0/sw/ARMCompiler6.12/bin/armar' +#strip = '/usr/arm-linux-gnueabihf/bin/strip' +#pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config' + +[properties] + +#c_args = ['--target=aarch64-arm-none-eabi'] + +[host_machine] +system = 'baremetal' +cpu_family = 'arm' +cpu = 'armv7' # Not sure if correct. +endian = 'little' diff --git a/docs/markdown/Generating-sources.md b/docs/markdown/Generating-sources.md index fe7d7ef..e22112f 100644 --- a/docs/markdown/Generating-sources.md +++ b/docs/markdown/Generating-sources.md @@ -59,14 +59,14 @@ foo_c = custom_target( output : 'foo.c', input : 'my_gen.py', command : [prog_python, '@INPUT@', '--code', '@OUTPUT@'], -] +) foo_h = custom_target( 'foo.h', output : 'foo.h', input : 'my_gen.py', command : [prog_python, '@INPUT@', '--header', '@OUTPUT@'], -] +) libfoo = static_library('foo', [foo_c, foo_h]) @@ -94,7 +94,7 @@ foo_ch = custom_target( output : ['foo.c', 'foo.h'], input : 'my_gen.py', command : [prog_python, '@INPUT@', '@OUTPUT@'], -] +) libfoo = static_library('foo', [foo_ch]) diff --git a/docs/markdown/Gnome-module.md b/docs/markdown/Gnome-module.md index dcd843f..9d8029e 100644 --- a/docs/markdown/Gnome-module.md +++ b/docs/markdown/Gnome-module.md @@ -339,10 +339,16 @@ of the module. * `scanobjs_args`: a list of arguments to pass to `gtkdoc-scangobj` * `c_args`: (*Added 0.48.0*) additional compile arguments to pass * `src_dir`: include_directories to include +* `check`: (*Since 0.52.0*) if `true` runs `gtkdoc-check` when running unit tests. + Note that this has the downside of rebuilding the doc for each build, which is + often very slow. It usually should be enabled only in CI. This creates a `$module-doc` target that can be ran to build docs and normally these are only built on install. +*Since 0.52.0* Returns a target object that can be passed as dependency to other +targets using generated doc files (e.g. in `content_files` of another doc). + ### gnome.gtkdoc_html_dir() Takes as argument a module name and returns the path where that diff --git a/docs/markdown/IDE-integration.md b/docs/markdown/IDE-integration.md index 21226c9..ec347d7 100644 --- a/docs/markdown/IDE-integration.md +++ b/docs/markdown/IDE-integration.md @@ -143,7 +143,8 @@ the `intro-buildoptions.json` file. Here is the JSON format for each option. "description": "the description", "type": "type ID", "value": "value depends on type", - "section": "section ID" + "section": "section ID", + "machine": "machine ID" } ``` @@ -168,6 +169,13 @@ The possible values for `section` are: - user - test +The `machine` key specifies the machine configuration for the option. Possible +values are: + + - any + - host + - build + To set the options, use the `meson configure` command. Since Meson 0.50.0 it is also possible to get the default buildoptions @@ -250,5 +258,6 @@ This API can also work without a build directory for the `--projectinfo` command # Existing integrations - [Gnome Builder](https://wiki.gnome.org/Apps/Builder) +- [KDevelop](https://www.kdevelop.org) - [Eclipse CDT](https://www.eclipse.org/cdt/) (experimental) - [Meson Cmake Wrapper](https://github.com/prozum/meson-cmake-wrapper) (for cmake IDEs) diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 195c451..6be3ed7 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -131,7 +131,7 @@ This function creates a new top-level target. Like all top-level targets, this integrates with the selected backend. For instance, with Ninja you can run it as `ninja target_name`. This is a dummy target that does not execute any command, but ensures that all dependencies are built. Dependencies can be any -build target (e.g. return value of executable(), custom_target(), etc) +build target (e.g. return value of [executable()](#executable), custom_target(), etc) ### assert() @@ -586,7 +586,7 @@ be passed to [shared and static libraries](#library). - `include_directories` one or more objects created with the `include_directories` function, or, since 0.50.0, strings, which will be transparently expanded to include directory objects -- `install`, when set to true, this executable should be installed +- `install`, when set to true, this executable should be installed, defaults to `false` - `install_dir` override install directory for this file. The value is relative to the `prefix` specified. F.ex, if you want to install plugins into a subdir, you'd use something like this: `install_dir : @@ -671,6 +671,14 @@ Keyword arguments are the following: [disabler object](#disabler-object) instead of a not-found object. *Since 0.49.0* +- `version` *(since 0.52.0)* Specifies the required version, see + [`dependency()`](#dependency) for argument format. The version of the program + is determined by running `program_name --version` command. If stdout is empty + it fallbacks to stderr. If the output contains more text than simply a version + number, only the first occurence of numbers separated by dots is kept. + If the output is more complicated than that, the version checking will have to + be done manually using [`run_command()`](#run_command). + Meson will also autodetect scripts with a shebang line and run them with the executable/interpreter specified in it both on Windows (because the command invocator will reject the command otherwise) and @@ -1334,6 +1342,13 @@ variables defined in the [`executable`](#executable) it is loaded by, you will need to set the `export_dynamic` argument of the executable to `true`. +Supports the following extra keyword arguments: + +- `vs_module_defs`, *(Added 0.52.0)*, a string, a File object, or + Custom Target for a Microsoft module definition file for controlling + symbol exports, etc., on platforms where that is possible + (e.g. Windows). + **Note:** Linking to a shared module is not supported on some platforms, notably OSX. Consider using a [`shared_library`](#shared_library) instead, if you need to both @@ -1779,6 +1794,7 @@ are immutable, all operations return their results as a new string. - `is_even()` returns true if the number is even - `is_odd()` returns true if the number is odd + - `to_string()` returns the value of the number as a string. ### `boolean` object @@ -2210,7 +2226,7 @@ an external dependency with the following methods: - sources: any compiled or static sources the dependency has - `get_variable(cmake : str, pkgconfig : str, configtool : str, - default_value : str, pkgconfig_define : [str, str]) *(Added in + default_value : str, pkgconfig_define : [str, str])` *(Added in 0.51.0)* A generic variable getter method, which repalces the get_*type*_variable methods. This allows one to get the variable from a dependency without knowing specifically how that dependency @@ -2238,7 +2254,7 @@ and has the following methods: - `path()` which returns a string pointing to the script or executable **NOTE:** You should not need to use this method. Passing the object - itself should work in all cases. F.ex.: `run_command(obj, arg1, arg2)` + itself should work in all cases. For example: `run_command(obj, arg1, arg2)` ### `environment` object diff --git a/docs/markdown/Release-notes-for-0.50.0.md b/docs/markdown/Release-notes-for-0.50.0.md index 44e8573..62a4b80 100644 --- a/docs/markdown/Release-notes-for-0.50.0.md +++ b/docs/markdown/Release-notes-for-0.50.0.md @@ -334,3 +334,9 @@ Meson now generates a `meson-info.json` file in the `meson-info` directory to provide introspection information about the latest meson run. This file is updated when the build configuration is changed and the build files are (re)generated. + +## New kwarg `install:` for `configure_file()` + +Previously when using `configure_file()`, you could install the outputted file +by setting the `install_dir:` keyword argument. Now, there is an explicit kwarg +`install:` to enable/disable it. Omitting it will maintain the old behaviour. diff --git a/docs/markdown/Release-notes-for-0.51.0.md b/docs/markdown/Release-notes-for-0.51.0.md index 86b2f70..b7e441c 100644 --- a/docs/markdown/Release-notes-for-0.51.0.md +++ b/docs/markdown/Release-notes-for-0.51.0.md @@ -110,7 +110,7 @@ dependency you have. ```meson dep = dependency('could_be_cmake_or_pkgconfig') # cmake returns 'YES', pkg-config returns 'ON' -if ['YES', 'ON'].contains(dep.get_variable(pkg-config : 'var-name', cmake : 'COP_VAR_NAME', default_value : 'NO')) +if ['YES', 'ON'].contains(dep.get_variable(pkgconfig : 'var-name', cmake : 'COP_VAR_NAME', default_value : 'NO')) error('Cannot build your project when dep is built with var-name support') endif ``` diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md index 7802b92..24d9deb 100644 --- a/docs/markdown/Syntax.md +++ b/docs/markdown/Syntax.md @@ -78,6 +78,13 @@ string_var = '42' num = string_var.to_int() ``` +Numbers can be converted to a string: + +```meson +int_var = 42 +string_var = int_var.to_string() +``` + Booleans -- diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index 694c190..3c27732 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -71,10 +71,23 @@ The simplest thing to do is just to run all tests, which is equivalent to runnin $ meson test ``` -You can also run only a single test by giving its name: +### Run subsets of tests + +For clarity, consider the meson.build containing: + +```meson + +test('A', ..., suite: 'foo') +test('B', ..., suite: 'foo') +test('C', ..., suite: 'bar') +test('D', ..., suite: 'baz') + +``` + +Specify test(s) by name like: ```console -$ meson test testname +$ meson test A D ``` Tests belonging to a suite `suite` can be run as follows @@ -85,6 +98,18 @@ $ meson test --suite (sub)project_name:suite Since version *0.46*, `(sub)project_name` can be omitted if it is the top-level project. +Multiple suites are specified like: + +```console +$ meson test --suite foo --suite bar +``` + +NOTE: If you choose to specify both suite(s) and specific test name(s), the +test name(s) must be contained in the suite(s). This however is redundant-- +it would be more useful to specify either specific test names or suite(s). + +### Other test options + Sometimes you need to run the tests multiple times, which is done like this: ```console @@ -127,4 +152,8 @@ Meson will report the output produced by the failing tests along with other usef For further information see the command line help of Meson by running `meson test -h`. -**NOTE:** If `meson test` does not work for you, you likely have a old version of Meson. In that case you should call `mesontest` instead. If `mesontest` doesn't work either you have a very old version prior to 0.37.0 and should upgrade. +## Legacy notes + +If `meson test` does not work for you, you likely have a old version of Meson. +In that case you should call `mesontest` instead. If `mesontest` doesn't work +either you have a very old version prior to 0.37.0 and should upgrade. diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md index be11cc0..37939dc 100644 --- a/docs/markdown/Users.md +++ b/docs/markdown/Users.md @@ -8,6 +8,7 @@ If you have a project that uses Meson that you want to add to this list, please listed in the [`meson` GitHub topic](https://github.com/topics/meson). - [2048.cpp](https://github.com/plibither8/2048.cpp), a fully featured terminal version of the game "2048" written in C++ + - [Aravis](https://github.com/AravisProject/aravis), a glib/gobject based library for video acquisition using Genicam cameras - [Akira](https://github.com/akiraux/Akira), a native Linux app for UI and UX design built in Vala and Gtk - [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) @@ -48,6 +49,7 @@ listed in the [`meson` GitHub topic](https://github.com/topics/meson). - [Hardcode-Tray](https://github.com/bil-elmoussaoui/Hardcode-Tray), fixes hardcoded tray icons in Linux - [HexChat](https://github.com/hexchat/hexchat), a cross-platform IRC client in C - [IGT](https://cgit.freedesktop.org/xorg/app/intel-gpu-tools/), Linux kernel graphics driver test suite + - [Irssi](https://github.com/irssi/irssi), a terminal chat client in C - [iSH](https://github.com/tbodt/ish), Linux shell for iOS - [Janet](https://github.com/janet-lang/janet), a functional and imperative programming language and bytecode interpreter - [json](https://github.com/nlohmann/json), JSON for Modern C++ diff --git a/docs/markdown/snippets/dist-command.md b/docs/markdown/snippets/dist-command.md new file mode 100644 index 0000000..026b8cd --- /dev/null +++ b/docs/markdown/snippets/dist-command.md @@ -0,0 +1,10 @@ +## Dist is now a top level command + +Previously creating a source archive could only be done with `ninja +dist`. Starting with this release Meson provides a top level `dist` +that can be invoked directly. It also has a command line option to +determine which kinds of archives to create: + +```meson +meson dist --formats=xztar,zip +``` diff --git a/docs/markdown/snippets/find_program_version.md b/docs/markdown/snippets/find_program_version.md new file mode 100644 index 0000000..04424d7 --- /dev/null +++ b/docs/markdown/snippets/find_program_version.md @@ -0,0 +1,9 @@ +## Version check in `find_program()` + +A new `version` keyword argument has been added to `find_program` to specify +the required version. See [`dependency()`](#dependency) for argument format. +The version of the program is determined by running `program_name --version` +command. If stdout is empty it fallbacks to stderr. If the output contains more +text than simply a version number, only the first occurence of numbers separated +by dots is kept. If the output is more complicated than that, the version +checking will have to be done manually using [`run_command()`](#run_command). diff --git a/docs/markdown/snippets/gtkdoc.md b/docs/markdown/snippets/gtkdoc.md new file mode 100644 index 0000000..f1f4ed4 --- /dev/null +++ b/docs/markdown/snippets/gtkdoc.md @@ -0,0 +1,11 @@ +## gtkdoc-check support + +`gnome.gtkdoc()` now has a `check` keyword argument. If `true` runs it will run +`gtkdoc-check` when running unit tests. Note that this has the downside of +rebuilding the doc for each build, which is often very slow. It usually should +be enabled only in CI. + +## `gnome.gtkdoc()` returns target object + +`gnome.gtkdoc()` now returns a target object that can be passed as dependency to +other targets using generated doc files (e.g. in `content_files` of another doc). diff --git a/docs/markdown/snippets/kconfig_enhancements.md b/docs/markdown/snippets/kconfig_enhancements.md new file mode 100644 index 0000000..94e3872 --- /dev/null +++ b/docs/markdown/snippets/kconfig_enhancements.md @@ -0,0 +1,3 @@ +## Enhancements to the kconfig module + +`kconfig.load()` may now accept a `configure_file()` as input file. diff --git a/docs/markdown/snippets/shared_module_defs.md b/docs/markdown/snippets/shared_module_defs.md new file mode 100644 index 0000000..5bc1de7 --- /dev/null +++ b/docs/markdown/snippets/shared_module_defs.md @@ -0,0 +1,4 @@ +## Added `vs_module_defs` to `shared_module()` + +Like `shared_library()`, `shared_module()` now accepts +`vs_module_defs` argument for controlling symbol exports, etc. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 0e490ab..13c717b 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -325,7 +325,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): for key, val in kwargs.items(): if isinstance(val, ElementaryNode): flattend_kwargs[key] = val.value - elif isinstance(val, (ArrayNode, ArgumentNode)): + elif isinstance(val, (ArrayNode, ArgumentNode, ArithmeticNode, MethodNode)): flattend_kwargs[key] = self.flatten_args(val, include_unknown_args) elif isinstance(val, (str, bool, int, float)) or include_unknown_args: flattend_kwargs[key] = val diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 617b140..aeb0400 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -158,16 +158,20 @@ class IntrospectionInterpreter(AstInterpreter): args = self.flatten_args(args) if not args or not isinstance(args[0], str): return - kwargs = self.flatten_kwargs(kwargs, True) name = args[0] srcqueue = [node] + + # Process the soruces BEFORE flattening the kwargs, to preserve the original nodes if 'sources' in kwargs: - srcqueue += kwargs['sources'] + srcqueue += mesonlib.listify(kwargs['sources']) + + kwargs = self.flatten_kwargs(kwargs, True) source_nodes = [] while srcqueue: curr = srcqueue.pop(0) arg_node = None + assert(isinstance(curr, BaseNode)) if isinstance(curr, FunctionNode): arg_node = curr.args elif isinstance(curr, ArrayNode): diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index b0bbbf5..b614b41 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -32,7 +32,7 @@ from .. import compilers from ..compilers import Compiler, CompilerArgs, CCompiler, VisualStudioLikeCompiler, FortranCompiler from ..linkers import ArLinker from ..mesonlib import ( - File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine + File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine, ProgressBar ) from ..mesonlib import get_compiler_for_source, has_path_sep from .backends import CleanTrees @@ -294,7 +294,7 @@ int dummy; self.build_elements = [] self.generate_phony() self.add_build_comment(NinjaComment('Build rules for targets')) - for t in self.build.get_targets().values(): + for t in ProgressBar(self.build.get_targets().values(), desc='Generating targets'): self.generate_target(t) self.add_build_comment(NinjaComment('Test rules')) self.generate_tests() @@ -883,7 +883,7 @@ int dummy; r.write(outfile) def write_builds(self, outfile): - for b in self.build_elements: + for b in ProgressBar(self.build_elements, desc='Writing build.ninja'): b.write(outfile) def generate_phony(self): @@ -2441,7 +2441,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # Add linker args for linking this target derived from 'base' build # options passed on the command-line, in default_options, etc. # These have the lowest priority. - if not isinstance(target, build.StaticLibrary): + if isinstance(target, build.StaticLibrary): + commands += linker.get_base_link_args(self.get_base_options_for_target(target)) + else: commands += compilers.get_base_link_args(self.get_base_options_for_target(target), linker, isinstance(target, build.SharedModule)) @@ -2617,11 +2619,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def generate_dist(self): elem = NinjaBuildElement(self.all_outputs, 'meson-dist', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('DESC', 'Creating source packages') - elem.add_item('COMMAND', self.environment.get_build_command() + [ - '--internal', 'dist', - self.environment.source_dir, - self.environment.build_dir, - ] + self.environment.get_build_command()) + elem.add_item('COMMAND', self.environment.get_build_command() + ['dist']) elem.add_item('pool', 'console') self.add_build(elem) # Alias that runs the target defined above diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 2e23a59..d5814ff 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -91,7 +91,7 @@ known_build_target_kwargs = ( known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'link_language', 'pie'} known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'} -known_shmod_kwargs = known_build_target_kwargs +known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'} known_stlib_kwargs = known_build_target_kwargs | {'pic'} known_jar_kwargs = known_exe_kwargs | {'main_class'} diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py index 01e1980..f9835a1 100644 --- a/mesonbuild/cmake/__init__.py +++ b/mesonbuild/cmake/__init__.py @@ -23,10 +23,12 @@ __all__ = [ 'CMakeTarget', 'CMakeTraceLine', 'CMakeTraceParser', + 'parse_generator_expressions', ] from .common import CMakeException from .client import CMakeClient from .executor import CMakeExecutor +from .generator import parse_generator_expressions from .interpreter import CMakeInterpreter from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser diff --git a/mesonbuild/cmake/generator.py b/mesonbuild/cmake/generator.py new file mode 100644 index 0000000..a30d2de --- /dev/null +++ b/mesonbuild/cmake/generator.py @@ -0,0 +1,129 @@ +# Copyright 2019 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 .. import mesonlib + +def parse_generator_expressions(raw: str) -> str: + '''Parse CMake generator expressions + + Most generator expressions are simply ignored for + simplicety, however some are required for some common + use cases. + ''' + + out = '' # type: str + i = 0 # type: int + + def equal(arg: str) -> str: + col_pos = arg.find(',') + if col_pos < 0: + return '0' + else: + return '1' if arg[:col_pos] == arg[col_pos + 1:] else '0' + + def vers_comp(op: str, arg: str) -> str: + col_pos = arg.find(',') + if col_pos < 0: + return '0' + else: + return '1' if mesonlib.version_compare(arg[:col_pos], '{}{}'.format(op, arg[col_pos + 1:])) else '0' + + supported = { + # Boolean functions + 'BOOL': lambda x: '0' if x.upper() in ['0', 'FALSE', 'OFF', 'N', 'NO', 'IGNORE', 'NOTFOUND'] or x.endswith('-NOTFOUND') else '1', + 'AND': lambda x: '1' if all([y == '1' for y in x.split(',')]) else '0', + 'OR': lambda x: '1' if any([y == '1' for y in x.split(',')]) else '0', + 'NOT': lambda x: '0' if x == '1' else '1', + + '0': lambda x: '', + '1': lambda x: x, + + # String operations + 'STREQUAL': equal, + 'EQUAL': equal, + 'VERSION_LESS': lambda x: vers_comp('<', x), + 'VERSION_GREATER': lambda x: vers_comp('>', x), + 'VERSION_EQUAL': lambda x: vers_comp('=', x), + 'VERSION_LESS_EQUAL': lambda x: vers_comp('<=', x), + 'VERSION_GREATER_EQUAL': lambda x: vers_comp('>=', x), + + # String modification + 'LOWER_CASE': lambda x: x.lower(), + 'UPPER_CASE': lambda x: x.upper(), + + # Always assume the BUILD_INTERFACE is valid. + # INSTALL_INTERFACE is always invalid for subprojects and + # it should also never appear in CMake config files, used + # for dependencies + 'INSTALL_INTERFACE': lambda x: '', + 'BUILD_INTERFACE': lambda x: x, + + # Constants + 'ANGLE-R': lambda x: '>', + 'COMMA': lambda x: ',', + 'SEMICOLON': lambda x: ';', + } + + # Recursively evaluate generator expressions + def eval_generator_expressions() -> str: + nonlocal i + i += 2 + + func = '' # type: str + args = '' # type: str + res = '' # type: str + exp = '' # type: str + + # Determine the body of the expression + while i < len(raw): + if raw[i] == '>': + # End of the generator expression + break + elif i < len(raw) - 1 and raw[i] == '$' and raw[i + 1] == '<': + # Nested generator expression + exp += eval_generator_expressions() + else: + # Generator expression body + exp += raw[i] + + i += 1 + + # Split the expression into a function and arguments part + col_pos = exp.find(':') + if col_pos < 0: + func = exp + else: + func = exp[:col_pos] + args = exp[col_pos + 1:] + + func = func.strip() + args = args.strip() + + # Evaluate the function + if func in supported: + res = supported[func](args) + + return res + + while i < len(raw): + if i < len(raw) - 1 and raw[i] == '$' and raw[i + 1] == '<': + # Generator expression detected --> try resolving it + out += eval_generator_expressions() + else: + # Normal string, leave unchanged + out += raw[i] + + i += 1 + + return out diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 28a8488..5687ad2 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -74,8 +74,11 @@ target_type_map = { 'SHARED_LIBRARY': 'shared_library', 'EXECUTABLE': 'executable', 'OBJECT_LIBRARY': 'static_library', + 'INTERFACE_LIBRARY': 'header_only' } +target_type_requires_trace = ['INTERFACE_LIBRARY'] + skip_targets = ['UTILITY'] blacklist_compiler_flags = [ @@ -138,6 +141,7 @@ class ConverterTarget: self.link_with = [] self.object_libs = [] self.compile_opts = {} + self.public_compile_opts = [] self.pie = False # Project default override options (c_std, cpp_std, etc.) @@ -170,7 +174,7 @@ class ConverterTarget: std_regex = re.compile(r'([-]{1,2}std=|/std:v?|[-]{1,2}std:)(.*)') - def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str) -> None: + def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str, trace: CMakeTraceParser) -> None: # Detect setting the C and C++ standard for i in ['c', 'cpp']: if i not in self.compile_opts: @@ -194,6 +198,18 @@ class ConverterTarget: if self.type.upper() == 'OBJECT_LIBRARY': self.pie = True + # Use the CMake trace, if required + if self.type.upper() in target_type_requires_trace: + if self.name in trace.targets: + props = trace.targets[self.name].properies + + self.includes += props.get('INTERFACE_INCLUDE_DIRECTORIES', []) + self.public_compile_opts += props.get('INTERFACE_COMPILE_DEFINITIONS', []) + self.public_compile_opts += props.get('INTERFACE_COMPILE_OPTIONS', []) + self.link_flags += props.get('INTERFACE_LINK_OPTIONS', []) + else: + mlog.warning('CMake: Target', mlog.bold(self.name), 'not found in CMake trace. This can lead to build errors') + # Fix link libraries temp = [] for i in self.link_libraries: @@ -584,7 +600,7 @@ class CMakeInterpreter: for i in self.custom_targets: i.postprocess(output_target_map, self.src_dir, self.subdir, self.build_dir) for i in self.targets: - i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix) + i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace) if i.type == 'OBJECT_LIBRARY': object_libs += [i] self.languages += [x for x in i.languages if x not in self.languages] @@ -762,17 +778,31 @@ class CMakeInterpreter: dep_kwargs = { 'link_args': tgt.link_flags + tgt.link_libraries, 'link_with': id_node(tgt_var), + 'compile_args': tgt.public_compile_opts, 'include_directories': id_node(inc_var), } # Generate the function nodes - inc_node = assign(inc_var, function('include_directories', tgt.includes)) - src_node = assign(src_var, function('files', sources)) - tgt_node = assign(tgt_var, function(tgt_func, [base_name, [id_node(src_var)] + generated], tgt_kwargs)) - dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + node_list = [] + if tgt_func == 'header_only': + del dep_kwargs['link_with'] + inc_node = assign(inc_var, function('include_directories', tgt.includes)) + dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + + node_list = [inc_node, dep_node] + src_var = '' + tgt_var = '' + + else: + inc_node = assign(inc_var, function('include_directories', tgt.includes)) + src_node = assign(src_var, function('files', sources)) + tgt_node = assign(tgt_var, function(tgt_func, [base_name, [id_node(src_var)] + generated], tgt_kwargs)) + dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + + node_list = [inc_node, src_node, tgt_node, dep_node] # Add the nodes to the ast - root_cb.lines += [inc_node, src_node, tgt_node, dep_node] + root_cb.lines += node_list processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var, 'func': tgt_func} def process_custom_target(tgt: ConverterCustomTarget) -> None: diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 4b87319..3a3f269 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -16,6 +16,7 @@ # or an interpreter-based tool. from .common import CMakeException +from .generator import parse_generator_expressions from .. import mlog from typing import List, Tuple, Optional @@ -81,7 +82,11 @@ class CMakeTraceParser: 'add_custom_command': self._cmake_add_custom_command, 'add_custom_target': self._cmake_add_custom_target, 'set_property': self._cmake_set_property, - 'set_target_properties': self._cmake_set_target_properties + 'set_target_properties': self._cmake_set_target_properties, + 'target_compile_definitions': self._cmake_target_compile_definitions, + 'target_compile_options': self._cmake_target_compile_options, + 'target_include_directories': self._cmake_target_include_directories, + 'target_link_options': self._cmake_target_link_options, } # Primary pass -- parse everything @@ -199,16 +204,23 @@ class CMakeTraceParser: args = list(tline.args) # Make a working copy # Make sure the lib is imported - if 'IMPORTED' not in args: - return self._gen_exception('add_library', 'non imported libraries are not supported', tline) + if 'INTERFACE' in args: + args.remove('INTERFACE') - args.remove('IMPORTED') + if len(args) < 1: + return self._gen_exception('add_library', 'interface library name not specified', tline) - # No only look at the first two arguments (target_name and target_type) and ignore the rest - if len(args) < 2: - return self._gen_exception('add_library', 'requires at least 2 arguments', tline) + self.targets[args[0]] = CMakeTarget(args[0], 'INTERFACE', {}) + elif 'IMPORTED' in args: + args.remove('IMPORTED') - self.targets[args[0]] = CMakeTarget(args[0], args[1], {}) + # No only look at the first two arguments (target_name and target_type) and ignore the rest + if len(args) < 2: + return self._gen_exception('add_library', 'requires at least 2 arguments', tline) + + self.targets[args[0]] = CMakeTarget(args[0], args[1], {}) + else: + return self._gen_exception('add_library', 'non imported / interface libraries are not supported', tline) def _cmake_add_custom_command(self, tline: CMakeTraceLine): # DOC: https://cmake.org/cmake/help/latest/command/add_custom_command.html @@ -343,8 +355,8 @@ class CMakeTraceParser: # set_property() this is not context free. There are two approaches I # can think of, both have drawbacks: # - # 1. Assume that the property will be capitalized, this is convention - # but cmake doesn't require it. + # 1. Assume that the property will be capitalized ([A-Z_]), this is + # convention but cmake doesn't require it. # 2. Maintain a copy of the list here: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties # # Neither of these is awesome for obvious reasons. I'm going to try @@ -354,8 +366,9 @@ class CMakeTraceParser: arglist = [] # type: List[Tuple[str, List[str]]] name = args.pop(0) values = [] + prop_regex = re.compile(r'^[A-Z_]+$') for a in args: - if a.isupper(): + if prop_regex.match(a): if values: arglist.append((name, ' '.join(values).split(';'))) name = a @@ -372,11 +385,70 @@ class CMakeTraceParser: self.targets[i].properies[name] = value + def _cmake_target_compile_definitions(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_compile_definitions.html + self._parse_common_target_options('target_compile_definitions', 'COMPILE_DEFINITIONS', 'INTERFACE_COMPILE_DEFINITIONS', tline) + + def _cmake_target_compile_options(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_compile_options.html + self._parse_common_target_options('target_compile_options', 'COMPILE_OPTIONS', 'INTERFACE_COMPILE_OPTIONS', tline) + + def _cmake_target_include_directories(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_include_directories.html + self._parse_common_target_options('target_include_directories', 'INCLUDE_DIRECTORIES', 'INTERFACE_INCLUDE_DIRECTORIES', tline, ignore=['SYSTEM', 'BEFORE'], paths=True) + + def _cmake_target_link_options(self, tline: CMakeTraceLine) -> None: + # DOC: https://cmake.org/cmake/help/latest/command/target_link_options.html + self._parse_common_target_options('target_link_options', 'LINK_OPTIONS', 'INTERFACE_LINK_OPTIONS', tline) + + def _parse_common_target_options(self, func: str, private_prop: str, interface_prop: str, tline: CMakeTraceLine, ignore: Optional[List[str]] = None, paths: bool = False): + if ignore is None: + ignore = ['BEFORE'] + + args = list(tline.args) + + if len(args) < 1: + return self._gen_exception(func, 'requires at least one argument', tline) + + target = args[0] + if target not in self.targets: + return self._gen_exception(func, 'TARGET {} not found'.format(target), tline) + + interface = [] + private = [] + + mode = 'PUBLIC' + for i in args[1:]: + if i in ignore: + continue + + if i in ['INTERFACE', 'PUBLIC', 'PRIVATE']: + mode = i + continue + + if mode in ['INTERFACE', 'PUBLIC']: + interface += [i] + + if mode in ['PUBLIC', 'PRIVATE']: + private += [i] + + if paths: + interface = self._guess_files(interface) + private = self._guess_files(private) + + interface = [x for x in interface if x] + private = [x for x in private if x] + + for i in [(private_prop, private), (interface_prop, interface)]: + if not i[0] in self.targets[target].properies: + self.targets[target].properies[i[0]] = [] + + self.targets[target].properies[i[0]] += i[1] + def _lex_trace(self, trace): # The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n' reg_tline = re.compile(r'\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(([\s\S]*?) ?\)\s*\n', re.MULTILINE) reg_other = re.compile(r'[^\n]*\n') - reg_genexp = re.compile(r'\$<.*>') loc = 0 while loc < len(trace): mo_file_line = reg_tline.match(trace, loc) @@ -394,9 +466,10 @@ class CMakeTraceParser: file = mo_file_line.group(1) line = mo_file_line.group(3) func = mo_file_line.group(4) - args = mo_file_line.group(5).split(' ') + args = mo_file_line.group(5) + args = parse_generator_expressions(args) + args = args.split(' ') args = list(map(lambda x: x.strip(), args)) - args = list(map(lambda x: reg_genexp.sub('', x), args)) # Remove generator expressions yield CMakeTraceLine(file, line, func, args) @@ -420,7 +493,7 @@ class CMakeTraceParser: # Abort concatination if curr_str no longer matches the regex fixed_list += [curr_str] curr_str = i - elif reg_end.match(i): + elif reg_end.match(i) or os.path.exists('{} {}'.format(curr_str, i)): # File detected curr_str = '{} {}'.format(curr_str, i) fixed_list += [curr_str] diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index 37e0ad1..6d9e814 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -33,7 +33,6 @@ __all__ = [ 'is_object', 'is_source', 'lang_suffixes', - 'sanitizer_compile_args', 'sort_clink', 'ArmCCompiler', @@ -116,7 +115,6 @@ from .compilers import ( is_object, is_library, lang_suffixes, - sanitizer_compile_args, sort_clink, CompilerArgs, ) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 47cfd98..eff7161 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -67,6 +67,7 @@ class CCompiler(CLikeCompiler, Compiler): #ifndef {symbol} {symbol}; #endif + return 0; }}''' return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 76c5c5e..3b61e61 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -12,9 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import abc, contextlib, enum, os.path, re, tempfile, shlex -from typing import List, Optional, Tuple -from pathlib import Path +import contextlib, enum, os.path, re, tempfile, shlex +from typing import Optional, Tuple, List from ..linkers import StaticLinker from .. import coredata @@ -22,7 +21,7 @@ from .. import mlog from .. import mesonlib from ..mesonlib import ( EnvironmentException, MachineChoice, MesonException, OrderedSet, - version_compare, Popen_safe + Popen_safe ) from ..envconfig import ( Properties, @@ -255,20 +254,6 @@ base_options = {'b_pch': coredata.UserBooleanOption('Use precompiled headers', T 'from_buildtype'), } -def sanitizer_compile_args(value): - if value == 'none': - return [] - args = ['-fsanitize=' + value] - if 'address' in value: # For -fsanitize=address,undefined - args.append('-fno-omit-frame-pointer') - return args - -def sanitizer_link_args(value): - if value == 'none': - return [] - args = ['-fsanitize=' + value] - return args - def option_enabled(boptions, options, option): try: if option not in boptions: @@ -279,10 +264,9 @@ def option_enabled(boptions, options, option): def get_base_compile_args(options, compiler): args = [] - # FIXME, gcc/clang specific. try: if options['b_lto'].value: - args.append('-flto') + args.extend(compiler.get_lto_compile_args()) except KeyError: pass try: @@ -290,7 +274,7 @@ def get_base_compile_args(options, compiler): except KeyError: pass try: - args += sanitizer_compile_args(options['b_sanitize'].value) + args += compiler.sanitizer_compile_args(options['b_sanitize'].value) except KeyError: pass try: @@ -329,14 +313,13 @@ def get_base_compile_args(options, compiler): def get_base_link_args(options, linker, is_shared_module): args = [] - # FIXME, gcc/clang specific. try: if options['b_lto'].value: - args.append('-flto') + args.extend(linker.get_lto_link_args()) except KeyError: pass try: - args += sanitizer_link_args(options['b_sanitize'].value) + args += linker.sanitizer_link_args(options['b_sanitize'].value) except KeyError: pass try: @@ -363,7 +346,7 @@ def get_base_link_args(options, linker, is_shared_module): args.append('-Wl,-bitcode_bundle') elif as_needed: # -Wl,-dead_strip_dylibs is incompatible with bitcode - args.append(linker.get_asneeded_args()) + args.extend(linker.get_asneeded_args()) try: crt_val = options['b_vscrt'].value buildtype = options['buildtype'].value @@ -575,9 +558,9 @@ class CompilerArgs(list): def append_direct(self, arg): ''' - Append the specified argument without any reordering or de-dup - except for absolute paths where the order of include search directories - is not relevant + Append the specified argument without any reordering or de-dup except + for absolute paths to libraries, etc, which can always be de-duped + safely. ''' if os.path.isabs(arg): self.append(arg) @@ -1198,6 +1181,18 @@ class Compiler: def remove_linkerlike_args(self, args): return [x for x in args if not x.startswith('-Wl')] + def get_lto_compile_args(self) -> List[str]: + return [] + + def get_lto_link_args(self) -> List[str]: + return [] + + def sanitizer_compile_args(self, value: str) -> List[str]: + return [] + + def sanitizer_link_args(self, value: str) -> List[str]: + return [] + @enum.unique class CompilerType(enum.Enum): diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 44f53eb..6ae2673 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -274,7 +274,6 @@ class PGICPPCompiler(PGICompiler, CPPCompiler): CPPCompiler.__init__(self, exelist, version, for_machine, is_cross, exe_wrapper, **kwargs) PGICompiler.__init__(self, compiler_type) - class ElbrusCPPCompiler(GnuCPPCompiler, ElbrusCompiler): def __init__(self, exelist, version, compiler_type, for_machine: MachineChoice, is_cross, exe_wrapper=None, defines=None, **kwargs): GnuCPPCompiler.__init__(self, exelist, version, compiler_type, for_machine, is_cross, exe_wrapper, defines, **kwargs) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index e417566..c10e2ca 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -321,6 +321,9 @@ class FlangFortranCompiler(ClangCompiler, FortranCompiler): '2': default_warn_args, '3': default_warn_args} + def language_stdlib_only_link_flags(self) -> List[str]: + return ['-lflang', '-lpgmath'] + class Open64FortranCompiler(FortranCompiler): def __init__(self, exelist, version, for_machine: MachineChoice, is_cross, exe_wrapper=None, **kwags): FortranCompiler.__init__(self, exelist, version, for_machine, is_cross, exe_wrapper, **kwags) diff --git a/mesonbuild/compilers/mixins/arm.py b/mesonbuild/compilers/mixins/arm.py index dfdf540..02654ce 100644 --- a/mesonbuild/compilers/mixins/arm.py +++ b/mesonbuild/compilers/mixins/arm.py @@ -161,8 +161,8 @@ class ArmclangCompiler: def __init__(self, compiler_type: 'CompilerType'): if not self.is_cross: raise mesonlib.EnvironmentException('armclang supports only cross-compilation.') - # Check whether 'armlink.exe' is available in path - self.linker_exe = 'armlink.exe' + # Check whether 'armlink' is available in path + self.linker_exe = 'armlink' args = '--vsn' try: p, stdo, stderr = mesonlib.Popen_safe(self.linker_exe, args) diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index e032402..37d2424 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -209,25 +209,25 @@ class CLikeCompiler: ''' return self.get_compiler_dirs(env, 'programs') - def get_pic_args(self): + def get_pic_args(self) -> typing.List[str]: return ['-fPIC'] - def name_string(self): + def name_string(self) -> str: return ' '.join(self.exelist) - def get_pch_use_args(self, pch_dir, header): + def get_pch_use_args(self, pch_dir: str, header: str) -> typing.List[str]: return ['-include', os.path.basename(header)] - def get_pch_name(self, header_name): + def get_pch_name(self, header_name: str) -> str: return os.path.basename(header_name) + '.' + self.get_pch_suffix() - def get_linker_search_args(self, dirname): + def get_linker_search_args(self, dirname: str) -> typing.List[str]: return ['-L' + dirname] def get_default_include_dirs(self): return [] - def gen_export_dynamic_link_args(self, env): + def gen_export_dynamic_link_args(self, env) -> typing.List[str]: m = env.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): return ['-Wl,--export-all-symbols'] @@ -236,7 +236,7 @@ class CLikeCompiler: else: return ['-Wl,-export-dynamic'] - def gen_import_library_args(self, implibname): + def gen_import_library_args(self, implibname: str) -> typing.List[str]: """ The name of the outputted import library @@ -332,6 +332,7 @@ class CLikeCompiler: #ifndef {symbol} {symbol}; #endif + return 0; }}''' return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) @@ -555,6 +556,7 @@ class CLikeCompiler: {prefix} int main() {{ {type} something; + return 0; }}''' if not self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies)[0]: @@ -633,6 +635,7 @@ class CLikeCompiler: #include <stdio.h> int main() {{ printf ("{fmt}", {cast} {f}()); + return 0; }}'''.format(**fargs) res = self.run(code, env, extra_args=extra_args, dependencies=dependencies) if not res.compiled: @@ -785,6 +788,7 @@ class CLikeCompiler: #error "No definition for __builtin_{func} found in the prefix" #endif #endif + return 0; }}''' return self.links(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index 46f04c4..9756604 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -188,15 +188,15 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): # All GCC-like backends can do assembly self.can_compile_suffixes.add('s') - def get_asneeded_args(self) -> str: + def get_asneeded_args(self) -> typing.List[str]: # 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 if self.compiler_type.is_osx_compiler: - return '-Wl,-dead_strip_dylibs' + return ['-Wl,-dead_strip_dylibs'] else: - return '-Wl,--as-needed' + return ['-Wl,--as-needed'] def get_pic_args(self) -> typing.List[str]: if self.compiler_type.is_osx_compiler or self.compiler_type.is_windows_compiler: @@ -366,6 +366,25 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta): return self._split_fetch_real_dirs(line.split('=', 1)[1]) return [] + def get_lto_compile_args(self) -> typing.List[str]: + return ['-flto'] + + def get_lto_link_args(self) -> typing.List[str]: + return ['-flto'] + + def sanitizer_compile_args(self, value: str) -> typing.List[str]: + if value == 'none': + return [] + args = ['-fsanitize=' + value] + if 'address' in value: # for -fsanitize=address,undefined + args.append('-fno-omit-frame-pointer') + return args + + def sanitizer_link_args(self, value: str) -> typing.List[str]: + if value == 'none': + return [] + return ['-fsanitize=' + value] + class GnuCompiler(GnuLikeCompiler): """ diff --git a/mesonbuild/compilers/mixins/intel.py b/mesonbuild/compilers/mixins/intel.py index f147c4c..7fadb50 100644 --- a/mesonbuild/compilers/mixins/intel.py +++ b/mesonbuild/compilers/mixins/intel.py @@ -22,14 +22,13 @@ import os import typing from ... import mesonlib +from ..compilers import CompilerType from .gnu import GnuLikeCompiler from .visualstudio import VisualStudioLikeCompiler if typing.TYPE_CHECKING: import subprocess # noqa: F401 - from ..compilers import CompilerType - # XXX: avoid circular dependencies # TODO: this belongs in a posix compiler class clike_optimization_args = { diff --git a/mesonbuild/compilers/mixins/pgi.py b/mesonbuild/compilers/mixins/pgi.py index a75c62d..0613e79 100644 --- a/mesonbuild/compilers/mixins/pgi.py +++ b/mesonbuild/compilers/mixins/pgi.py @@ -16,6 +16,7 @@ import typing import os +from pathlib import Path from ..compilers import clike_debug_args, clike_optimization_args @@ -42,8 +43,9 @@ pgi_buildtype_linker_args = { } # type: typing.Dict[str, typing.List[str]] -class PGICompiler: +class PGICompiler(): def __init__(self, compiler_type: 'CompilerType'): + self.base_options = ['b_pch'] self.id = 'pgi' self.compiler_type = compiler_type @@ -59,9 +61,21 @@ class PGICompiler: def get_no_warn_args(self) -> typing.List[str]: return ['-silent'] + def gen_import_library_args(self, implibname: str) -> typing.List[str]: + return [] + + def get_std_shared_lib_link_args(self) -> typing.List[str]: + # PGI -shared is Linux only. + if self.compiler_type.is_windows_compiler: + return ['-Bdynamic', '-Mmakedll'] + elif not self.compiler_type.is_osx_compiler: + return ['-shared'] + return [] + def get_pic_args(self) -> typing.List[str]: + # PGI -fPIC is Linux only. if self.compiler_type.is_osx_compiler or self.compiler_type.is_windows_compiler: - return [] # PGI -fPIC is Linux only. + return [] return ['-fPIC'] def openmp_flags(self) -> typing.List[str]: @@ -93,3 +107,17 @@ class PGICompiler: def get_always_args(self) -> typing.List[str]: return [] + + def get_pch_suffix(self) -> str: + # PGI defaults to .pch suffix for PCH on Linux and Windows with --pch option + return 'pch' + + def get_pch_use_args(self, pch_dir: str, header: str) -> typing.List[str]: + # PGI supports PCH for C++ only. + hdr = Path(pch_dir).resolve().parent / header + if self.language == 'cpp': + return ['--pch', + '--pch_dir', str(hdr.parent), + '-I{}'.format(hdr.parent)] + else: + return [] diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index 0a612ae..d8734fc 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -38,7 +38,7 @@ class ValaCompiler(Compiler): return [] def get_debug_args(self, is_debug): - return ['--debug'] + return ['--debug'] if is_debug else [] def get_output_args(self, target): return [] # Because compiles into C. diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 1a397c7..048a29c 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -35,6 +35,8 @@ import enum if typing.TYPE_CHECKING: from . import dependencies + OptionDictType = typing.Dict[str, 'UserOption[Any]'] + version = '0.51.999' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'vs2019', 'xcode'] @@ -387,14 +389,17 @@ class CoreData: if not filenames: return [] - real = [] + found_invalid = [] # type: typing.List[str] + missing = [] # type: typing.List[str] + real = [] # type: typing.List[str] for i, f in enumerate(filenames): f = os.path.expanduser(os.path.expandvars(f)) if os.path.exists(f): if os.path.isfile(f): real.append(os.path.abspath(f)) + continue elif os.path.isdir(f): - raise MesonException('Cross and native files must not be directories') + found_invalid.append(os.path.abspath(f)) else: # in this case we've been passed some kind of pipe, copy # the contents of that file into the meson private (scratch) @@ -408,8 +413,8 @@ class CoreData: # Also replace the command line argument, as the pipe # probably wont exist on reconfigure filenames[i] = copy - continue - elif sys.platform != 'win32': + continue + if sys.platform != 'win32': paths = [ os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share')), ] + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':') @@ -419,9 +424,14 @@ class CoreData: real.append(path_to_try) break else: - raise MesonException('Cannot find specified {} file: {}'.format(ftype, f)) - continue + missing.append(f) + else: + missing.append(f) + if missing: + if found_invalid: + mlog.log('Found invalid candidates for', ftype, 'file:', *found_invalid) + mlog.log('Could not find any valid candidate for', ftype, 'files:', *missing) raise MesonException('Cannot find specified {} file: {}'.format(ftype, f)) return real diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 8616367..22f4c6f 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1035,6 +1035,12 @@ class CMakeDependency(ExternalDependency): cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path)) pref_path = self.env.coredata.builtins_per_machine[self.for_machine]['cmake_prefix_path'].value + if 'CMAKE_PREFIX_PATH' in os.environ: + env_pref_path = os.environ['CMAKE_PREFIX_PATH'].split(':') + env_pref_path = [x for x in env_pref_path if x] # Filter out enpty strings + if not pref_path: + pref_path = [] + pref_path += env_pref_path if pref_path: cm_args.append('-DCMAKE_PREFIX_PATH={}'.format(';'.join(pref_path))) @@ -1290,15 +1296,18 @@ class CMakeDependency(ExternalDependency): # Failed to guess a target --> try the old-style method if len(modules) == 0: - incDirs = self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS') - defs = self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS') - libs = self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') + incDirs = [x for x in self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS') if x] + defs = [x for x in self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS') if x] + libs = [x for x in self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') if x] # Try to use old style variables if no module is specified if len(libs) > 0: self.compile_args = list(map(lambda x: '-I{}'.format(x), incDirs)) + defs self.link_args = libs mlog.debug('using old-style CMake variables for dependency {}'.format(name)) + mlog.debug('Include Dirs: {}'.format(incDirs)) + mlog.debug('Compiler Definitions: {}'.format(defs)) + mlog.debug('Libraries: {}'.format(libs)) return # Even the old-style approach failed. Nothing else we can do here @@ -1340,39 +1349,37 @@ class CMakeDependency(ExternalDependency): mlog.debug(tgt) if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properies: - incDirs += tgt.properies['INTERFACE_INCLUDE_DIRECTORIES'] + incDirs += [x for x in tgt.properies['INTERFACE_INCLUDE_DIRECTORIES'] if x] if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properies: - tempDefs = list(tgt.properies['INTERFACE_COMPILE_DEFINITIONS']) - tempDefs = list(map(lambda x: '-D{}'.format(re.sub('^-D', '', x)), tempDefs)) - compileDefinitions += tempDefs + compileDefinitions += ['-D' + re.sub('^-D', '', x) for x in tgt.properies['INTERFACE_COMPILE_DEFINITIONS'] if x] if 'INTERFACE_COMPILE_OPTIONS' in tgt.properies: - compileOptions += tgt.properies['INTERFACE_COMPILE_OPTIONS'] + compileOptions += [x for x in tgt.properies['INTERFACE_COMPILE_OPTIONS'] if x] if 'IMPORTED_CONFIGURATIONS' in tgt.properies: - cfgs = tgt.properies['IMPORTED_CONFIGURATIONS'] + cfgs = [x for x in tgt.properies['IMPORTED_CONFIGURATIONS'] if x] cfg = cfgs[0] if 'RELEASE' in cfgs: cfg = 'RELEASE' if 'IMPORTED_IMPLIB_{}'.format(cfg) in tgt.properies: - libraries += tgt.properies['IMPORTED_IMPLIB_{}'.format(cfg)] + libraries += [x for x in tgt.properies['IMPORTED_IMPLIB_{}'.format(cfg)] if x] elif 'IMPORTED_IMPLIB' in tgt.properies: - libraries += tgt.properies['IMPORTED_IMPLIB'] + libraries += [x for x in tgt.properies['IMPORTED_IMPLIB'] if x] elif 'IMPORTED_LOCATION_{}'.format(cfg) in tgt.properies: - libraries += tgt.properies['IMPORTED_LOCATION_{}'.format(cfg)] + libraries += [x for x in tgt.properies['IMPORTED_LOCATION_{}'.format(cfg)] if x] elif 'IMPORTED_LOCATION' in tgt.properies: - libraries += tgt.properies['IMPORTED_LOCATION'] + libraries += [x for x in tgt.properies['IMPORTED_LOCATION'] if x] if 'INTERFACE_LINK_LIBRARIES' in tgt.properies: - otherDeps += tgt.properies['INTERFACE_LINK_LIBRARIES'] + otherDeps += [x for x in tgt.properies['INTERFACE_LINK_LIBRARIES'] if x] if 'IMPORTED_LINK_DEPENDENT_LIBRARIES_{}'.format(cfg) in tgt.properies: - otherDeps += tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES_{}'.format(cfg)] + otherDeps += [x for x in tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES_{}'.format(cfg)] if x] elif 'IMPORTED_LINK_DEPENDENT_LIBRARIES' in tgt.properies: - otherDeps += tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES'] + otherDeps += [x for x in tgt.properies['IMPORTED_LINK_DEPENDENT_LIBRARIES'] if x] for j in otherDeps: if j in self.traceparser.targets: @@ -1391,23 +1398,19 @@ class CMakeDependency(ExternalDependency): mlog.debug('Compiler Options: {}'.format(compileOptions)) mlog.debug('Libraries: {}'.format(libraries)) - self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs)) + self.compile_args = compileOptions + compileDefinitions + ['-I{}'.format(x) for x in incDirs] self.link_args = libraries def _setup_cmake_dir(self, cmake_file: str) -> str: # Setup the CMake build environment and return the "build" directory - build_dir = '{}/cmake_{}'.format(self.cmake_root_dir, self.name) - os.makedirs(build_dir, exist_ok=True) + build_dir = Path(self.cmake_root_dir) / 'cmake_{}'.format(self.name) + build_dir.mkdir(parents=True, exist_ok=True) # Copy the CMakeLists.txt - cmake_lists = '{}/CMakeLists.txt'.format(build_dir) - dir_path = os.path.dirname(os.path.realpath(__file__)) - src_cmake = '{}/data/{}'.format(dir_path, cmake_file) - if os.path.exists(cmake_lists): - os.remove(cmake_lists) - shutil.copyfile(src_cmake, cmake_lists) + src_cmake = Path(__file__).parent / 'data' / cmake_file + shutil.copyfile(str(src_cmake), str(build_dir / 'CMakeLists.txt')) # str() is for Python 3.5 - return build_dir + return str(build_dir) def _call_cmake(self, args, cmake_file: str, env=None): build_dir = self._setup_cmake_dir(cmake_file) @@ -1542,7 +1545,7 @@ class DubDependency(ExternalDependency): for lib in target['buildSettings'][field_name]: if lib not in libs: libs.append(lib) - if os.name is not 'nt': + if os.name != 'nt': pkgdep = PkgConfigDependency(lib, environment, {'required': 'true', 'silent': 'true'}) for arg in pkgdep.get_compile_args(): self.compile_args.append(arg) diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 1bb1b6e..53c3747 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -370,20 +370,30 @@ class OpenMPDependency(ExternalDependency): language = kwargs.get('language') super().__init__('openmp', environment, language, kwargs) self.is_found = False + if self.clib_compiler.get_id() == 'pgi': + # through at least PGI 19.4, there is no macro defined for OpenMP, but OpenMP 3.1 is supported. + self.version = '3.1' + self.is_found = True + self.compile_args = self.link_args = self.clib_compiler.openmp_flags() + return try: openmp_date = self.clib_compiler.get_define( '_OPENMP', '', self.env, self.clib_compiler.openmp_flags(), [self], disable_cache=True)[0] except mesonlib.EnvironmentException as e: mlog.debug('OpenMP support not available in the compiler') mlog.debug(e) - openmp_date = False + openmp_date = None if openmp_date: self.version = self.VERSIONS[openmp_date] - if self.clib_compiler.has_header('omp.h', '', self.env, dependencies=[self], disable_cache=True)[0]: - self.is_found = True - self.compile_args = self.link_args = self.clib_compiler.openmp_flags() - else: + # Flang has omp_lib.h + header_names = ('omp.h', 'omp_lib.h') + for name in header_names: + if self.clib_compiler.has_header(name, '', self.env, dependencies=[self], disable_cache=True)[0]: + self.is_found = True + self.compile_args = self.link_args = self.clib_compiler.openmp_flags() + break + if not self.is_found: mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.') diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index a47208d..6c30fc1 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -763,7 +763,6 @@ class Environment: target = 'x86' if 'IA-32' in err else 'x86_64' cls = IntelClCCompiler if lang == 'c' else IntelClCPPCompiler return cls(compiler, version, for_machine, is_cross, exe_wrap, target) - return cls(ccache + compiler, version, compiler_type, for_machine, is_cross, exe_wrap, full_version=full_version) if 'Microsoft' in out or 'Microsoft' in err: # Latest versions of Visual Studio print version # number to stderr but earlier ones print version diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index fd94251..0891094 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -21,7 +21,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, MachineChoice, PerMachine, Popen_safe, listify, extract_as_list, has_path_sep +from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException from .interpreterbase import InterpreterBase @@ -40,7 +40,7 @@ from collections import namedtuple from itertools import chain from pathlib import PurePath import functools -from typing import Sequence, List, Union, Optional, Iterator, Dict, Any +from typing import Sequence, List, Union, Optional, Dict, Any import importlib @@ -444,7 +444,7 @@ class DependencyHolder(InterpreterObject, ObjectHolder): @FeatureNew('dep.get_variable', '0.51.0') @noPosargs - @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'default', 'pkgconfig_define'}) + @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'default_value', 'pkgconfig_define'}) def variable_method(self, args, kwargs): return self.held_object.get_variable(**kwargs) @@ -489,6 +489,7 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): ObjectHolder.__init__(self, ep) self.methods.update({'found': self.found_method, 'path': self.path_method}) + self.cached_version = None @noPosargs @permittedKwargs({}) @@ -509,6 +510,24 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): def get_name(self): return self.held_object.get_name() + def get_version(self, interpreter): + if not self.cached_version: + raw_cmd = self.get_command() + ['--version'] + cmd = [self, '--version'] + res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) + if res.returncode != 0: + m = 'Running {!r} failed' + raise InterpreterException(m.format(raw_cmd)) + output = res.stdout.strip() + if not output: + output = res.stderr.strip() + match = re.search(r'([0-9\.]+)', output) + if not match: + m = 'Could not find a version number in output of {!r}' + raise InterpreterException(m.format(raw_cmd)) + self.cached_version = match.group(1) + return self.cached_version + class ExternalLibraryHolder(InterpreterObject, ObjectHolder): def __init__(self, el, pv): InterpreterObject.__init__(self) @@ -1993,7 +2012,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'version', }, 'executable': build.known_exe_kwargs, - 'find_program': {'required', 'native'}, + 'find_program': {'required', 'native', 'version'}, 'generator': {'arguments', 'output', 'depends', @@ -2879,7 +2898,7 @@ external dependencies (including libraries) must go to "dependencies".''') # TODO update modules to always pass `for_machine`. It is bad-form to assume # the host machine. - def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, required=True, silent=True): + def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, required=True, silent=True, wanted=''): if not isinstance(args, list): args = [args] @@ -2897,8 +2916,20 @@ external dependencies (including libraries) must go to "dependencies".''') return ExternalProgramHolder(dependencies.NonExistingExternalProgram()) # Only store successful lookups self.store_name_lookups(args) + if wanted: + version = progobj.get_version(self) + is_found, not_found, found = mesonlib.version_compare_many(version, wanted) + if not is_found: + mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), + 'found {!r} but need:'.format(version), + ', '.join(["'{}'".format(e) for e in not_found])) + if required: + m = 'Invalid version of program, need {!r} {!r} found {!r}.' + raise InvalidArguments(m.format(progobj.get_name(), not_found, version)) + return ExternalProgramHolder(dependencies.NonExistingExternalProgram()) return progobj + @FeatureNewKwargs('find_program', '0.52.0', ['version']) @FeatureNewKwargs('find_program', '0.49.0', ['disabler']) @disablerIfNotFound @permittedKwargs(permitted_kwargs['find_program']) @@ -2913,8 +2944,9 @@ external dependencies (including libraries) must go to "dependencies".''') if not isinstance(required, bool): raise InvalidArguments('"required" argument must be a boolean.') + wanted = mesonlib.stringlistify(kwargs.get('version', [])) for_machine = self.machine_from_native_kwarg(kwargs) - return self.find_program_impl(args, for_machine, required=required, silent=False) + return self.find_program_impl(args, for_machine, required=required, silent=False, wanted=wanted) def func_find_library(self, node, args, kwargs): raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n' diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 71a4ef3..b5510cf 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -337,20 +337,25 @@ class Disabler(InterpreterObject): def found_method(self, args, kwargs): return False -def is_disabler(i): +def is_disabler(i) -> bool: return isinstance(i, Disabler) -def is_disabled(args, kwargs): +def is_arg_disabled(arg) -> bool: + if is_disabler(arg): + return True + if isinstance(arg, list): + for i in arg: + if is_arg_disabled(i): + return True + return False + +def is_disabled(args, kwargs) -> bool: for i in args: - if isinstance(i, Disabler): + if is_arg_disabled(i): return True for i in kwargs.values(): - if isinstance(i, Disabler): + if is_arg_disabled(i): return True - if isinstance(i, list): - for j in i: - if isinstance(j, Disabler): - return True return False class InterpreterBase: diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 648d1ef..dc9a825 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -12,173 +12,161 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .mesonlib import Popen_safe, is_windows +import typing + from . import mesonlib +if typing.TYPE_CHECKING: + from .coredata import OptionDictType + from .environment import Environment + + class StaticLinker: - def can_linker_accept_rsp(self): + + def __init__(self, exelist: typing.List[str]): + self.exelist = exelist + + def can_linker_accept_rsp(self) -> bool: """ Determines whether the linker can accept arguments using the @rsp syntax. """ return mesonlib.is_windows() + def get_base_link_args(self, options: 'OptionDictType') -> typing.List[str]: + """Like compilers.get_base_link_args, but for the static linker.""" + return [] -class VisualStudioLikeLinker: - always_args = ['/NOLOGO'] - - def __init__(self, exelist, machine): - self.exelist = exelist - self.machine = machine - - def get_exelist(self): + def get_exelist(self) -> typing.List[str]: return self.exelist.copy() - def get_std_link_args(self): + def get_std_link_args(self) -> typing.List[str]: return [] - def get_buildtype_linker_args(self, buildtype): + def get_buildtype_linker_args(self, buildtype: str) -> typing.List[str]: return [] - def get_output_args(self, target): - args = [] - if self.machine: - args += ['/MACHINE:' + self.machine] - args += ['/OUT:' + target] - return args + def get_output_args(self, target: str) -> typing.List[str]: + return[] - def get_coverage_link_args(self): + def get_coverage_link_args(self) -> typing.List[str]: return [] - def get_always_args(self): - return self.always_args.copy() + def build_rpath_args(self, build_dir: str, from_dir: str, rpath_paths: str, + build_rpath: str, install_rpath: str) -> typing.List[str]: + return [] - def get_linker_always_args(self): - return self.always_args.copy() + def thread_link_flags(self, env: 'Environment') -> typing.List[str]: + return [] + + def openmp_flags(self) -> typing.List[str]: + return [] - def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): + def get_option_link_args(self, options: 'OptionDictType') -> typing.List[str]: return [] - def thread_link_flags(self, env): + @classmethod + def unix_args_to_native(cls, args: typing.List[str]) -> typing.List[str]: + return args + + def get_link_debugfile_args(self, targetfile: str) -> typing.List[str]: + # Static libraries do not have PDB files return [] - def openmp_flags(self): + def get_always_args(self) -> typing.List[str]: return [] - def get_option_link_args(self, options): + def get_linker_always_args(self) -> typing.List[str]: return [] + +class VisualStudioLikeLinker: + always_args = ['/NOLOGO'] + + def __init__(self, machine: str): + self.machine = machine + + def get_always_args(self) -> typing.List[str]: + return self.always_args.copy() + + def get_linker_always_args(self) -> typing.List[str]: + return self.always_args.copy() + + def get_output_args(self, target: str) -> typing.List[str]: + args = [] # type: typing.List[str] + if self.machine: + args += ['/MACHINE:' + self.machine] + args += ['/OUT:' + target] + return args + @classmethod - def unix_args_to_native(cls, args): + def unix_args_to_native(cls, args: typing.List[str]) -> typing.List[str]: from .compilers import VisualStudioCCompiler return VisualStudioCCompiler.unix_args_to_native(args) - def get_link_debugfile_args(self, targetfile): - # Static libraries do not have PDB files - return [] - class VisualStudioLinker(VisualStudioLikeLinker, StaticLinker): """Microsoft's lib static linker.""" + def __init__(self, exelist: typing.List[str], machine: str): + StaticLinker.__init__(self, exelist) + VisualStudioLikeLinker.__init__(self, machine) + class IntelVisualStudioLinker(VisualStudioLikeLinker, StaticLinker): """Intel's xilib static linker.""" + def __init__(self, exelist: typing.List[str], machine: str): + StaticLinker.__init__(self, exelist) + VisualStudioLikeLinker.__init__(self, machine) + class ArLinker(StaticLinker): - def __init__(self, exelist): - self.exelist = exelist + def __init__(self, exelist: typing.List[str]): + super().__init__(exelist) self.id = 'ar' - pc, stdo = Popen_safe(self.exelist + ['-h'])[0:2] + pc, stdo = mesonlib.Popen_safe(self.exelist + ['-h'])[0:2] # Enable deterministic builds if they are available. if '[D]' in stdo: self.std_args = ['csrD'] else: self.std_args = ['csr'] - def can_linker_accept_rsp(self): - return mesonlib.is_windows() - - def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): - return [] - - def get_exelist(self): - return self.exelist[:] - - def get_std_link_args(self): + def get_std_link_args(self) -> typing.List[str]: return self.std_args - def get_output_args(self, target): + def get_output_args(self, target: str) -> typing.List[str]: return [target] - def get_buildtype_linker_args(self, buildtype): - return [] - - def get_linker_always_args(self): - return [] - - def get_coverage_link_args(self): - return [] - - def get_always_args(self): - return [] - - def thread_link_flags(self, env): - return [] - - def openmp_flags(self): - return [] - - def get_option_link_args(self, options): - return [] - - @classmethod - def unix_args_to_native(cls, args): - return args[:] - - def get_link_debugfile_args(self, targetfile): - return [] class ArmarLinker(ArLinker): - def __init__(self, exelist): - self.exelist = exelist + def __init__(self, exelist: typing.List[str]): + StaticLinker.__init__(self, exelist) self.id = 'armar' self.std_args = ['-csr'] - def can_linker_accept_rsp(self): + def can_linker_accept_rsp(self) -> bool: # armar cann't accept arguments using the @rsp syntax return False + class DLinker(StaticLinker): - def __init__(self, exelist, arch): - self.exelist = exelist + def __init__(self, exelist: typing.List[str], arch: str): + super().__init__(exelist) self.id = exelist[0] self.arch = arch - def can_linker_accept_rsp(self): - return mesonlib.is_windows() - - def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): - return [] - - def get_exelist(self): - return self.exelist[:] - - def get_std_link_args(self): + def get_std_link_args(self) -> typing.List[str]: return ['-lib'] - def get_output_args(self, target): + def get_output_args(self, target: str) -> typing.List[str]: return ['-of=' + target] - def get_buildtype_linker_args(self, buildtype): - return [] - - def get_linker_always_args(self): - if is_windows(): + def get_linker_always_args(self) -> typing.List[str]: + if mesonlib.is_windows(): if self.arch == 'x86_64': return ['-m64'] elif self.arch == 'x86_mscoff' and self.id == 'dmd': @@ -186,75 +174,18 @@ class DLinker(StaticLinker): return ['-m32'] return [] - def get_coverage_link_args(self): - return [] - - def get_always_args(self): - return [] - - def thread_link_flags(self, env): - return [] - - def openmp_flags(self): - return [] - - def get_option_link_args(self, options): - return [] - - @classmethod - def unix_args_to_native(cls, args): - return args[:] - - def get_link_debugfile_args(self, targetfile): - return [] class CcrxLinker(StaticLinker): - def __init__(self, exelist): - self.exelist = exelist + def __init__(self, exelist: typing.List[str]): + super().__init__(exelist) self.id = 'rlink' - pc, stdo = Popen_safe(self.exelist + ['-h'])[0:2] - self.std_args = [] - def can_linker_accept_rsp(self): + def can_linker_accept_rsp(self) -> bool: return False - def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): - return [] - - def get_exelist(self): - return self.exelist[:] - - def get_std_link_args(self): - return self.std_args - - def get_output_args(self, target): + def get_output_args(self, target: str) -> typing.List[str]: return ['-output=%s' % target] - def get_buildtype_linker_args(self, buildtype): - return [] - - def get_linker_always_args(self): + def get_linker_always_args(self) -> typing.List[str]: return ['-nologo', '-form=library'] - - def get_coverage_link_args(self): - return [] - - def get_always_args(self): - return [] - - def thread_link_flags(self, env): - return [] - - def openmp_flags(self): - return [] - - def get_option_link_args(self, options): - return [] - - @classmethod - def unix_args_to_native(cls, args): - return args[:] - - def get_link_debugfile_args(self, targetfile): - return [] diff --git a/mesonbuild/scripts/dist.py b/mesonbuild/mdist.py index 1a62c70..2d1aaf9 100644 --- a/mesonbuild/scripts/dist.py +++ b/mesonbuild/mdist.py @@ -14,18 +14,27 @@ import lzma +import gzip import os import sys import shutil import subprocess -import pickle import hashlib -import tarfile, zipfile -import tempfile +import json from glob import glob from mesonbuild.environment import detect_ninja from mesonbuild.mesonlib import windows_proof_rmtree -from mesonbuild import mlog +from mesonbuild import mlog, build + +archive_choices = ['gztar', 'xztar', 'zip'] +archive_extension = {'gztar': '.tar.gz', + 'xztar': '.tar.xz', + 'zip': '.zip'} + +def add_arguments(parser): + parser.add_argument('--formats', default='xztar', + help='Comma separated list of archive types to create.') + def create_hash(fname): hashname = fname + '.sha256sum' @@ -35,22 +44,6 @@ def create_hash(fname): f.write('%s %s\n' % (m.hexdigest(), os.path.basename(fname))) -def create_zip(zipfilename, packaging_dir): - prefix = os.path.dirname(packaging_dir) - removelen = len(prefix) + 1 - with zipfile.ZipFile(zipfilename, - 'w', - compression=zipfile.ZIP_DEFLATED, - allowZip64=True) as zf: - zf.write(packaging_dir, packaging_dir[removelen:]) - for root, dirs, files in os.walk(packaging_dir): - for d in dirs: - dname = os.path.join(root, d) - zf.write(dname, dname[removelen:]) - for f in files: - fname = os.path.join(root, f) - zf.write(fname, fname[removelen:]) - def del_gitfiles(dirname): for f in glob(os.path.join(dirname, '.git*')): if os.path.isdir(f) and not os.path.islink(f): @@ -98,7 +91,7 @@ def git_have_dirty_index(src_root): ret = subprocess.call(['git', '-C', src_root, 'diff-index', '--quiet', 'HEAD']) return ret == 1 -def create_dist_git(dist_name, src_root, bld_root, dist_sub, dist_scripts): +def create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, dist_scripts): if git_have_dirty_index(src_root): mlog.warning('Repository has uncommitted changes that will not be included in the dist tarball') distdir = os.path.join(dist_sub, dist_name) @@ -109,15 +102,13 @@ def create_dist_git(dist_name, src_root, bld_root, dist_sub, dist_scripts): process_submodules(distdir) del_gitfiles(distdir) run_dist_scripts(distdir, dist_scripts) - xzname = distdir + '.tar.xz' - # Should use shutil but it got xz support only in 3.5. - with tarfile.open(xzname, 'w:xz') as tf: - tf.add(distdir, dist_name) - # Create only .tar.xz for now. - # zipname = distdir + '.zip' - # create_zip(zipname, distdir) + output_names = [] + for a in archives: + compressed_name = distdir + archive_extension[a] + shutil.make_archive(distdir, a, root_dir=dist_sub, base_dir=dist_name) + output_names.append(compressed_name) shutil.rmtree(distdir) - return (xzname, ) + return output_names def hg_have_dirty_index(src_root): @@ -125,26 +116,35 @@ def hg_have_dirty_index(src_root): out = subprocess.check_output(['hg', '-R', src_root, 'summary']) return b'commit: (clean)' not in out -def create_dist_hg(dist_name, src_root, bld_root, dist_sub, dist_scripts): +def create_dist_hg(dist_name, archives, src_root, bld_root, dist_sub, dist_scripts): if hg_have_dirty_index(src_root): mlog.warning('Repository has uncommitted changes that will not be included in the dist tarball') os.makedirs(dist_sub, exist_ok=True) tarname = os.path.join(dist_sub, dist_name + '.tar') xzname = tarname + '.xz' + gzname = tarname + '.gz' + zipname = os.path.join(dist_sub, dist_name + '.zip') subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname]) + output_names = [] if dist_scripts: mlog.warning('dist scripts are not supported in Mercurial projects') - with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: - shutil.copyfileobj(tf, xf) + if 'xztar' in archives: + with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: + shutil.copyfileobj(tf, xf) + output_names.append(xzname) + if 'gztar' in archives: + with gzip.open(gzname, 'wb') as zf, open(tarname, 'rb') as tf: + shutil.copyfileobj(tf, zf) + output_names.append(gzname) os.unlink(tarname) - # Create only .tar.xz for now. - # zipname = os.path.join(dist_sub, dist_name + '.zip') - # subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname]) - return (xzname, ) + if 'zip' in archives: + subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname]) + output_names.append(zipname) + return output_names -def check_dist(packagename, meson_command, privdir): +def check_dist(packagename, meson_command, bld_root, privdir): print('Testing distribution package %s' % packagename) unpackdir = os.path.join(privdir, 'dist-unpack') builddir = os.path.join(privdir, 'dist-build') @@ -155,10 +155,14 @@ def check_dist(packagename, meson_command, privdir): os.mkdir(p) ninja_bin = detect_ninja() try: - tf = tarfile.open(packagename) - tf.extractall(unpackdir) - srcdir = glob(os.path.join(unpackdir, '*'))[0] - if subprocess.call(meson_command + ['--backend=ninja', srcdir, builddir]) != 0: + shutil.unpack_archive(packagename, unpackdir) + unpacked_files = glob(os.path.join(unpackdir, '*')) + assert(len(unpacked_files) == 1) + unpacked_src_dir = unpacked_files[0] + with open(os.path.join(bld_root, 'meson-info', 'intro-buildoptions.json')) as boptions: + meson_command += ['-D{name}={value}'.format(**o) for o in json.load(boptions) + if o['name'] not in ['backend', 'install_umask']] + if subprocess.call(meson_command + ['--backend=ninja', unpacked_src_dir, builddir]) != 0: print('Running Meson on distribution package failed') return 1 if subprocess.call([ninja_bin], cwd=builddir) != 0: @@ -179,33 +183,43 @@ def check_dist(packagename, meson_command, privdir): print('Distribution package %s tested' % packagename) return 0 -def run(args): - src_root = args[0] - bld_root = args[1] - meson_command = args[2:] +def determine_archives_to_generate(options): + result = [] + for i in options.formats.split(','): + if i not in archive_choices: + sys.exit('Value "{}" not one of permitted values {}.'.format(i, archive_choices)) + result.append(i) + if len(i) == 0: + sys.exit('No archive types specified.') + return result + +def run(options): + b = build.load('.') + # This import must be load delayed, otherwise it will get the default + # value of None. + from mesonbuild.mesonlib import meson_command + src_root = b.environment.source_dir + bld_root = b.environment.build_dir priv_dir = os.path.join(bld_root, 'meson-private') dist_sub = os.path.join(bld_root, 'meson-dist') - buildfile = os.path.join(priv_dir, 'build.dat') - - build = pickle.load(open(buildfile, 'rb')) + dist_name = b.project_name + '-' + b.project_version - dist_name = build.project_name + '-' + build.project_version + archives = determine_archives_to_generate(options) _git = os.path.join(src_root, '.git') if os.path.isdir(_git) or os.path.isfile(_git): - names = create_dist_git(dist_name, src_root, bld_root, dist_sub, build.dist_scripts) + names = create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, b.dist_scripts) elif os.path.isdir(os.path.join(src_root, '.hg')): - names = create_dist_hg(dist_name, src_root, bld_root, dist_sub, build.dist_scripts) + names = create_dist_hg(dist_name, archives, src_root, bld_root, dist_sub, b.dist_scripts) else: print('Dist currently only works with Git or Mercurial repos') return 1 if names is None: return 1 - error_count = 0 - for name in names: - rc = check_dist(name, meson_command, priv_dir) # Check only one. - if rc == 0: + # Check only one. + rc = check_dist(names[0], meson_command, bld_root, priv_dir) + if rc == 0: + for name in names: create_hash(name) - error_count += rc - return 1 if error_count else 0 + return rc diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 585a5bb..1223b2c 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -957,7 +957,10 @@ def expand_arguments(args): return None return expended_args -def Popen_safe(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): +def Popen_safe(args: typing.List[str], write: typing.Optional[str] = None, + stdout: typing.Union[typing.BinaryIO, int] = subprocess.PIPE, + stderr: typing.Union[typing.BinaryIO, int] = subprocess.PIPE, + **kwargs: typing.Any) -> typing.Tuple[subprocess.Popen, str, str]: import locale encoding = locale.getpreferredencoding() if sys.version_info < (3, 6) or not sys.stdout.encoding or encoding.upper() != 'UTF-8': @@ -967,12 +970,16 @@ def Popen_safe(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, o, e = p.communicate(write) return p, o, e -def Popen_safe_legacy(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): +def Popen_safe_legacy(args: typing.List[str], write: typing.Optional[str] = None, + stdout: typing.Union[typing.BinaryIO, int] = subprocess.PIPE, + stderr: typing.Union[typing.BinaryIO, int] = subprocess.PIPE, + **kwargs: typing.Any) -> typing.Tuple[subprocess.Popen, str, str]: p = subprocess.Popen(args, universal_newlines=False, close_fds=False, stdout=stdout, stderr=stderr, **kwargs) + input_ = None # type: typing.Optional[bytes] if write is not None: - write = write.encode('utf-8') - o, e = p.communicate(write) + input_ = write.encode('utf-8') + o, e = p.communicate(input_) if o is not None: if sys.stdout.encoding: o = o.decode(encoding=sys.stdout.encoding, errors='replace').replace('\r\n', '\n') @@ -1306,3 +1313,58 @@ class LibType(Enum): STATIC = 1 PREFER_SHARED = 2 PREFER_STATIC = 3 + + +class ProgressBarFallback: + '''Fallback progress bar implementation when tqdm is not found''' + def __init__(self, iterable=None, total=None, bar_type=None, desc=None): + if iterable is not None: + self.iterable = iter(iterable) + return + self.total = total + self.done = 0 + self.printed_dots = 0 + if self.total and bar_type == 'download': + print('Download size:', self.total) + if desc: + print('{}: '.format(desc), end='') + + # Pretend to be an iterator when called as one and don't print any + # progress + def __iter__(self): + return self.iterable + + def __next__(self): + return next(self.iterable) + + def print_dot(self): + print('.', end='') + sys.stdout.flush() + self.printed_dots += 1 + + def update(self, progress): + self.done += progress + if not self.total: + # Just print one dot per call if we don't have a total length + self.print_dot() + return + ratio = int(self.done / self.total * 10) + while self.printed_dots < ratio: + self.print_dot() + + def close(self): + print('') + +try: + from tqdm import tqdm + + class ProgressBar(tqdm): + def __init__(self, *args, bar_type=None, **kwargs): + if bar_type == 'download': + kwargs.update({'unit': 'bytes', 'leave': True}) + else: + kwargs.update({'leave': False}) + kwargs['ncols'] = 100 + super().__init__(*args, **kwargs) +except ImportError: + ProgressBar = ProgressBarFallback diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index c94f1bf..06214ad 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -22,7 +22,7 @@ import shutil from . import mesonlib from . import mlog -from . import mconf, minit, minstall, mintro, msetup, mtest, rewriter, msubprojects, munstable_coredata +from . import mconf, mdist, minit, minstall, mintro, msetup, mtest, rewriter, msubprojects, munstable_coredata from .mesonlib import MesonException from .environment import detect_msys2_arch from .wrap import wraptool @@ -44,6 +44,8 @@ class CommandLineParser: help_msg='Configure the project') self.add_command('configure', mconf.add_arguments, mconf.run, help_msg='Change project options',) + self.add_command('dist', mdist.add_arguments, mdist.run, + help_msg='Generate release archive',) self.add_command('install', minstall.add_arguments, minstall.run, help_msg='Install the project') self.add_command('introspect', mintro.add_arguments, mintro.run, diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index e81a8e1..1d30149 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -229,31 +229,33 @@ def list_buildoptions(coredata: cdata.CoreData) -> List[dict]: core_options = {k: o for k, o in coredata.builtins.items() if k in core_option_names} add_keys(optlist, core_options, 'core') - add_keys(optlist, coredata.builtins_per_machine.host, 'core (for host machine)') + add_keys(optlist, coredata.builtins_per_machine.host, 'core', machine='host') add_keys( optlist, {'build.' + k: o for k, o in coredata.builtins_per_machine.build.items()}, - 'core (for build machine)', + 'core', + machine='build', ) add_keys(optlist, coredata.backend_options, 'backend') add_keys(optlist, coredata.base_options, 'base') - add_keys(optlist, coredata.compiler_options.host, 'compiler (for host machine)') + add_keys(optlist, coredata.compiler_options.host, 'compiler', machine='host') add_keys( optlist, {'build.' + k: o for k, o in coredata.compiler_options.build.items()}, - 'compiler (for build machine)', + 'compiler', + machine='build', ) add_keys(optlist, dir_options, 'directory') add_keys(optlist, coredata.user_options, 'user') add_keys(optlist, test_options, 'test') return optlist -def add_keys(optlist, options: Dict[str, cdata.UserOption], section): +def add_keys(optlist, options: Dict[str, cdata.UserOption], section: str, machine: str = 'any'): keys = list(options.keys()) keys.sort() for key in keys: opt = options[key] - optdict = {'name': key, 'value': opt.value, 'section': section} + optdict = {'name': key, 'value': opt.value, 'section': section, 'machine': machine} if isinstance(opt, cdata.UserStringOption): typestr = 'string' elif isinstance(opt, cdata.UserBooleanOption): diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index aa815c7..1ee98cc 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -23,7 +23,6 @@ import subprocess from .. import build from .. import mlog from .. import mesonlib -from .. import compilers from .. import interpreter from . import GResourceTarget, GResourceHeaderTarget, GirTarget, TypelibTarget, VapiTarget from . import get_include_args @@ -604,7 +603,7 @@ class GnomeModule(ExtensionModule): cflags += state.project_args[lang] if 'b_sanitize' in compiler.base_options: sanitize = state.environment.coredata.base_options['b_sanitize'].value - cflags += compilers.sanitizer_compile_args(sanitize) + cflags += compiler.sanitizer_compile_args(sanitize) sanitize = sanitize.split(',') # These must be first in ldflags if 'address' in sanitize: @@ -615,7 +614,7 @@ class GnomeModule(ExtensionModule): internal_ldflags += ['-lubsan'] # FIXME: Linking directly to lib*san is not recommended but g-ir-scanner # does not understand -f LDFLAGS. https://bugzilla.gnome.org/show_bug.cgi?id=783892 - # ldflags += compilers.sanitizer_link_args(sanitize) + # ldflags += compiler.sanitizer_link_args(sanitize) return cflags, internal_ldflags, external_ldflags @@ -913,6 +912,7 @@ This will become a hard error in the future.''') rv = [inscript, pottarget, potarget] return ModuleReturnValue(None, rv) + @FeatureNewKwargs('gnome.gtkdoc', '0.52.0', ['check']) @FeatureNewKwargs('gnome.gtkdoc', '0.48.0', ['c_args']) @FeatureNewKwargs('gnome.gtkdoc', '0.48.0', ['module_version']) @FeatureNewKwargs('gnome.gtkdoc', '0.37.0', ['namespace', 'mode']) @@ -1019,10 +1019,26 @@ This will become a hard error in the future.''') args += self._unpack_args('--ignore-headers=', 'ignore_headers', kwargs) args += self._unpack_args('--installdir=', 'install_dir', kwargs) args += self._get_build_args(kwargs, state, depends) - res = [build.RunTarget(targetname, command[0], command[1:] + args, depends, state.subdir, state.subproject)] + custom_kwargs = {'output': modulename + '-decl.txt', + 'command': command + args, + 'depends': depends, + 'build_always_stale': True, + } + custom_target = build.CustomTarget(targetname, state.subdir, state.subproject, custom_kwargs) + alias_target = build.AliasTarget(targetname, [custom_target], state.subdir, state.subproject) + if kwargs.get('check', False): + check_cmd = self.interpreter.find_program_impl('gtkdoc-check') + check_env = ['DOC_MODULE=' + modulename, + 'DOC_MAIN_SGML_FILE=' + main_file] + check_args = [targetname + '-check', check_cmd] + check_kwargs = {'env': check_env, + 'workdir': os.path.join(state.environment.get_build_dir(), state.subdir), + 'depends': custom_target} + self.interpreter.add_test(state.current_node, check_args, check_kwargs, True) + res = [custom_target, alias_target] if kwargs.get('install', True): res.append(build.RunScript(command, args)) - return ModuleReturnValue(None, res) + return ModuleReturnValue(custom_target, res) def _get_build_args(self, kwargs, state, depends): args = [] diff --git a/mesonbuild/modules/unstable_kconfig.py b/mesonbuild/modules/unstable_kconfig.py index 1639eed..6685710 100644 --- a/mesonbuild/modules/unstable_kconfig.py +++ b/mesonbuild/modules/unstable_kconfig.py @@ -54,15 +54,16 @@ class KconfigModule(ExtensionModule): raise InvalidCode('load takes only one file input.') s = sources[0] + is_built = False if isinstance(s, mesonlib.File): - # kconfig input is processed at "meson setup" time, not during - # the build, so it cannot reside in the build directory. if s.is_built: - raise InvalidCode('kconfig input must be a source file.') - s = s.relative_name() + FeatureNew('kconfig.load() of built files', '0.52.0').use(state.subproject) + is_built = True + s = s.absolute_path(interpreter.environment.source_dir, interpreter.environment.build_dir) + else: + s = os.path.join(interpreter.environment.source_dir, s) - s = os.path.join(interpreter.environment.source_dir, s) - if s not in interpreter.build_def_files: + if s not in interpreter.build_def_files and not is_built: interpreter.build_def_files.append(s) return self._load_file(s) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 253f4ab..dc5c9d1 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -14,6 +14,7 @@ # A tool to run tests in many different ways. +from pathlib import Path from collections import namedtuple from copy import deepcopy import argparse @@ -426,18 +427,18 @@ def run_with_mono(fname: str) -> bool: return False def load_benchmarks(build_dir: str) -> typing.List['TestSerialisation']: - datafile = os.path.join(build_dir, 'meson-private', 'meson_benchmark_setup.dat') - if not os.path.isfile(datafile): - raise TestException('Directory ${!r} does not seem to be a Meson build directory.'.format(build_dir)) - with open(datafile, 'rb') as f: + datafile = Path(build_dir) / 'meson-private' / 'meson_benchmark_setup.dat' + if not datafile.is_file(): + raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir)) + with datafile.open('rb') as f: obj = typing.cast(typing.List['TestSerialisation'], pickle.load(f)) return obj def load_tests(build_dir: str) -> typing.List['TestSerialisation']: - datafile = os.path.join(build_dir, 'meson-private', 'meson_test_setup.dat') - if not os.path.isfile(datafile): - raise TestException('Directory ${!r} does not seem to be a Meson build directory.'.format(build_dir)) - with open(datafile, 'rb') as f: + datafile = Path(build_dir) / 'meson-private' / 'meson_test_setup.dat' + if not datafile.is_file(): + raise TestException('Directory {!r} does not seem to be a Meson build directory.'.format(build_dir)) + with datafile.open('rb') as f: obj = typing.cast(typing.List['TestSerialisation'], pickle.load(f)) return obj @@ -833,8 +834,9 @@ Timeout: %4d return False def test_suitable(self, test: 'TestSerialisation') -> bool: - return (not self.options.include_suites or TestHarness.test_in_suites(test, self.options.include_suites)) \ - and not TestHarness.test_in_suites(test, self.options.exclude_suites) + return ((not self.options.include_suites or + TestHarness.test_in_suites(test, self.options.include_suites)) and not + TestHarness.test_in_suites(test, self.options.exclude_suites)) def get_tests(self) -> typing.List['TestSerialisation']: if not self.tests: @@ -849,6 +851,7 @@ Timeout: %4d else: tests = self.tests + # allow specifying test names like "meson test foo1 foo2", where test('foo1', ...) if self.options.args: tests = [t for t in tests if t.name in self.options.args] @@ -975,7 +978,7 @@ def list_tests(th: TestHarness) -> bool: return not tests def rebuild_all(wd: str) -> bool: - if not os.path.isfile(os.path.join(wd, 'build.ninja')): + if not (Path(wd) / 'build.ninja').is_file(): print('Only ninja backend is supported to rebuild tests before running them.') return True @@ -984,11 +987,9 @@ def rebuild_all(wd: str) -> bool: print("Can't find ninja, can't rebuild test.") return False - p = subprocess.Popen([ninja, '-C', wd]) - p.communicate() - - if p.returncode != 0: - print('Could not rebuild') + ret = subprocess.run([ninja, '-C', wd]).returncode + if ret != 0: + print('Could not rebuild {}'.format(wd)) return False return True diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index cc4669c..9925f4e 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -413,9 +413,8 @@ def fix_darwin(fname, new_rpath, final_path, install_name_mappings): subprocess.check_call(['install_name_tool', fname] + args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except Exception: - raise - sys.exit(0) + except Exception as err: + raise SystemExit(err) def fix_jar(fname): subprocess.check_call(['jar', 'xfv', fname, 'META-INF/MANIFEST.MF']) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 55f86bc..4b2a323 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -18,8 +18,9 @@ import urllib.request, os, hashlib, shutil, tempfile, stat import subprocess import sys import configparser + from . import WrapMode -from ..mesonlib import MesonException +from ..mesonlib import ProgressBar, MesonException try: import ssl @@ -278,24 +279,17 @@ class Resolver: tmpfile.write(block) hashvalue = h.hexdigest() return hashvalue, tmpfile.name - print('Download size:', dlsize) - print('Downloading: ', end='') sys.stdout.flush() - printed_dots = 0 - downloaded = 0 + progress_bar = ProgressBar(bar_type='download', total=dlsize, + desc='Downloading') while True: block = resp.read(blocksize) if block == b'': break - downloaded += len(block) h.update(block) tmpfile.write(block) - ratio = int(downloaded / dlsize * 10) - while printed_dots < ratio: - print('.', end='') - sys.stdout.flush() - printed_dots += 1 - print('') + progress_bar.update(len(block)) + progress_bar.close() hashvalue = h.hexdigest() return hashvalue, tmpfile.name diff --git a/run_project_tests.py b/run_project_tests.py index 797e1a9..a161525 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Tuple +import typing import itertools import os import subprocess @@ -35,6 +35,7 @@ from mesonbuild import mtest from mesonbuild.mesonlib import MachineChoice, stringlistify, Popen_safe from mesonbuild.coredata import backendlist import argparse +import json import xml.etree.ElementTree as ET import time import multiprocessing @@ -108,18 +109,10 @@ def setup_commands(optbackend): compile_commands, clean_commands, test_commands, install_commands, \ uninstall_commands = get_backend_commands(backend, do_debug) -def get_relative_files_list_from_dir(fromdir): - paths = [] - for (root, _, files) in os.walk(fromdir): - reldir = os.path.relpath(root, start=fromdir) - for f in files: - path = os.path.join(reldir, f).replace('\\', '/') - if path.startswith('./'): - path = path[2:] - paths.append(path) - return paths - -def platform_fix_name(fname, compiler, env): +def get_relative_files_list_from_dir(fromdir: Path) -> typing.List[Path]: + return [file.relative_to(fromdir) for file in fromdir.rglob('*') if file.is_file()] + +def platform_fix_name(fname: str, compiler, env) -> str: # canonicalize compiler if compiler in {'clang-cl', 'intel-cl'}: canonical_compiler = 'msvc' @@ -199,35 +192,36 @@ def platform_fix_name(fname, compiler, env): return fname -def validate_install(srcdir, installdir, compiler, env): +def validate_install(srcdir: str, installdir: Path, compiler, env) -> str: # List of installed files - info_file = os.path.join(srcdir, 'installed_files.txt') + info_file = Path(srcdir) / 'installed_files.txt' + installdir = Path(installdir) # If this exists, the test does not install any other files - noinst_file = 'usr/no-installed-files' - expected = {} + noinst_file = Path('usr/no-installed-files') + expected = {} # type: typing.Dict[Path, bool] ret_msg = '' # Generate list of expected files - if os.path.exists(os.path.join(installdir, noinst_file)): + if (installdir / noinst_file).is_file(): expected[noinst_file] = False - elif os.path.exists(info_file): - with open(info_file) as f: + elif info_file.is_file(): + with info_file.open() as f: for line in f: line = platform_fix_name(line.strip(), compiler, env) if line: - expected[line] = False + expected[Path(line)] = False # Check if expected files were found for fname in expected: - file_path = os.path.join(installdir, fname) - if os.path.exists(file_path) or os.path.islink(file_path): + file_path = installdir / fname + if file_path.is_file() or file_path.is_symlink(): expected[fname] = True for (fname, found) in expected.items(): if not found: - ret_msg += 'Expected file {0} missing.\n'.format(fname) + ret_msg += 'Expected file {} missing.\n'.format(fname) # Check if there are any unexpected files found = get_relative_files_list_from_dir(installdir) for fname in found: if fname not in expected: - ret_msg += 'Extra file {0} found.\n'.format(fname) + ret_msg += 'Extra file {} found.\n'.format(fname) if ret_msg != '': ret_msg += '\nInstall dir contents:\n' for i in found: @@ -348,6 +342,7 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen compile_commands, clean_commands, install_commands, uninstall_commands = commands test_args = parse_test_args(testdir) gen_start = time.time() + setup_env = None # Configure in-process if pass_prefix_to_test(testdir): gen_args = ['--prefix', '/usr'] @@ -362,7 +357,15 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen crossfile = os.path.join(testdir, 'crossfile.ini') if os.path.exists(crossfile): gen_args.extend(['--cross-file', crossfile]) - (returncode, stdo, stde) = run_configure(gen_args) + setup_env_file = os.path.join(testdir, 'setup_env.json') + if os.path.exists(setup_env_file): + setup_env = os.environ.copy() + with open(setup_env_file, 'r') as fp: + data = json.load(fp) + for key, val in data.items(): + val = val.replace('@ROOT@', os.path.abspath(testdir)) + setup_env[key] = val + (returncode, stdo, stde) = run_configure(gen_args, env=setup_env) try: logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt') mesonlog = logfile.open(errors='ignore', encoding='utf-8').read() @@ -435,7 +438,7 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen return TestResult(validate_install(testdir, install_dir, compiler, builddata.environment), BuildStep.validate, stdo, stde, mesonlog, gen_time, build_time, test_time) -def gather_tests(testdir: Path) -> List[Path]: +def gather_tests(testdir: Path) -> typing.List[Path]: test_names = [t.name for t in testdir.glob('*') if t.is_dir()] test_names = [t for t in test_names if not t.startswith('.')] # Filter non-tests files (dot files, etc) test_nums = [(int(t.split()[0]), t) for t in test_names] @@ -588,7 +591,7 @@ def should_skip_rust() -> bool: return True return False -def detect_tests_to_run(only: List[str]) -> List[Tuple[str, List[Path], bool]]: +def detect_tests_to_run(only: typing.List[str]) -> typing.List[typing.Tuple[str, typing.List[Path], bool]]: """ Parameters ---------- @@ -601,10 +604,8 @@ def detect_tests_to_run(only: List[str]) -> List[Tuple[str, List[Path], bool]]: tests to run """ - ninja_fortran_compiler = shutil.which('gfortran') or shutil.which('flang') or shutil.which('pgfortran') or (not mesonlib.is_windows() and shutil.which('ifort')) - ninja_fortran = backend is Backend.ninja and ninja_fortran_compiler - vs_fortran = mesonlib.is_windows() and backend is Backend.vs and shutil.which('ifort') - skip_fortran = not(ninja_fortran or vs_fortran) + skip_fortran = not(shutil.which('gfortran') or shutil.which('flang') or + shutil.which('pgfortran') or shutil.which('ifort')) # Name, subdirectory, skip condition. all_tests = [ @@ -627,7 +628,7 @@ def detect_tests_to_run(only: List[str]) -> List[Tuple[str, List[Path], bool]]: ('d', 'd', backend is not Backend.ninja or not have_d_compiler()), ('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or not have_objc_compiler()), ('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or not have_objcpp_compiler()), - ('fortran', 'fortran', skip_fortran), + ('fortran', 'fortran', skip_fortran or backend != Backend.ninja), ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), ('cuda', 'cuda', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('nvcc')), ('python3', 'python3', backend is not Backend.ninja), @@ -644,14 +645,14 @@ def detect_tests_to_run(only: List[str]) -> List[Tuple[str, List[Path], bool]]: gathered_tests = [(name, gather_tests(Path('test cases', subdir)), skip) for name, subdir, skip in all_tests] return gathered_tests -def run_tests(all_tests, log_name_base, failfast, extra_args): +def run_tests(all_tests, log_name_base, failfast: bool, extra_args): global logfile txtname = log_name_base + '.txt' with open(txtname, 'w', encoding='utf-8', errors='ignore') as lf: logfile = lf return _run_tests(all_tests, log_name_base, failfast, extra_args) -def _run_tests(all_tests, log_name_base, failfast, extra_args): +def _run_tests(all_tests, log_name_base, failfast: bool, extra_args): global stop, executor, futures, system_compiler xmlname = log_name_base + '.xml' junit_root = ET.Element('testsuites') @@ -757,8 +758,8 @@ def _run_tests(all_tests, log_name_base, failfast, extra_args): stdeel = ET.SubElement(current_test, 'system-err') stdeel.text = result.stde - if failfast and failing_tests > 0: - break + if failfast and failing_tests > 0: + break print("\nTotal configuration time: %.2fs" % conf_time) print("Total build time: %.2fs" % build_time) diff --git a/run_tests.py b/run_tests.py index 051b91e..6a42681 100755 --- a/run_tests.py +++ b/run_tests.py @@ -34,11 +34,13 @@ from mesonbuild import mlog from mesonbuild.environment import Environment, detect_ninja from mesonbuild.coredata import backendlist -def guess_backend(backend, msbuild_exe): +def guess_backend(backend, msbuild_exe: str): # Auto-detect backend if unspecified backend_flags = [] if backend is None: - if msbuild_exe is not None and mesonlib.is_windows(): + if (msbuild_exe is not None and + mesonlib.is_windows() and not + (os.environ.get('CC') == 'icl' or os.environ.get('CXX') == 'icl' or os.environ.get('FC') == 'ifort')): backend = 'vs' # Meson will auto-detect VS version to use else: backend = 'ninja' @@ -222,28 +224,33 @@ def clear_meson_configure_class_caches(): mesonbuild.dependencies.PkgConfigDependency.pkgbin_cache = {} mesonbuild.dependencies.PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None) -def run_configure_inprocess(commandlist): +def run_configure_inprocess(commandlist, env=None): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() old_stderr = sys.stderr sys.stderr = mystderr = StringIO() + old_environ = os.environ.copy() + if env is not None: + os.environ.update(env) try: returncode = mesonmain.run(commandlist, get_meson_script()) finally: sys.stdout = old_stdout sys.stderr = old_stderr clear_meson_configure_class_caches() + os.environ.clear() + os.environ.update(old_environ) return returncode, mystdout.getvalue(), mystderr.getvalue() -def run_configure_external(full_command): - pc, o, e = mesonlib.Popen_safe(full_command) +def run_configure_external(full_command, env=None): + pc, o, e = mesonlib.Popen_safe(full_command, env=env) return pc.returncode, o, e -def run_configure(commandlist): +def run_configure(commandlist, env=None): global meson_exe if meson_exe: - return run_configure_external(meson_exe + commandlist) - return run_configure_inprocess(commandlist) + return run_configure_external(meson_exe + commandlist, env=env) + return run_configure_inprocess(commandlist, env=env) def print_system_info(): print(mlog.bold('System information.').get_text(mlog.colorize_console)) diff --git a/run_unittests.py b/run_unittests.py index 93c9083..0bd2c02 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -30,6 +30,8 @@ import functools import io import operator import threading +import urllib.error +import urllib.request from itertools import chain from unittest import mock from configparser import ConfigParser @@ -66,6 +68,10 @@ from run_tests import ( run_configure_inprocess, run_mtest_inprocess ) + +URLOPEN_TIMEOUT = 5 + + def get_dynamic_section_entry(fname, entry): if is_cygwin() or is_osx(): raise unittest.SkipTest('Test only applicable to ELF platforms') @@ -1126,10 +1132,10 @@ class DataTests(unittest.TestCase): if f not in exceptions: self.assertIn(f, toc) - def test_syntax_highlighting_files(self): + def test_vim_syntax_highlighting(self): ''' - Ensure that syntax highlighting files were updated for new functions in - the global namespace in build files. + Ensure that vim syntax highlighting files were updated for new + functions in the global namespace in build files. ''' env = get_fake_env() interp = Interpreter(FakeBuild(env), mock=True) @@ -1138,6 +1144,41 @@ class DataTests(unittest.TestCase): defined = set([a.strip() for a in res.group().split('\\')][1:]) self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) + def test_json_grammar_syntax_highlighting(self): + ''' + Ensure that syntax highlighting JSON grammar written by TingPing was + updated for new functions in the global namespace in build files. + https://github.com/TingPing/language-meson/ + ''' + env = get_fake_env() + interp = Interpreter(FakeBuild(env), mock=True) + url = 'https://raw.githubusercontent.com/TingPing/language-meson/master/grammars/meson.json' + try: + # Use a timeout to avoid blocking forever in case the network is + # slow or unavailable in a weird way + r = urllib.request.urlopen(url, timeout=URLOPEN_TIMEOUT) + except urllib.error.URLError as e: + # Skip test when network is not available, such as during packaging + # by a distro or Flatpak + if not isinstance(e, urllib.error.HTTPError): + raise unittest.SkipTest('Network unavailable') + # Don't fail the test if github is down, but do fail if 4xx + if e.code >= 500: + raise unittest.SkipTest('Server error ' + str(e.code)) + raise e + # On Python 3.5, we must decode bytes to string. Newer versions don't require that. + grammar = json.loads(r.read().decode('utf-8', 'surrogatepass')) + for each in grammar['patterns']: + if 'name' in each and each['name'] == 'support.function.builtin.meson': + # The string is of the form: (?x)\\b(func1|func2|...\n)\\b\\s*(?=\\() and + # we convert that to [func1, func2, ...] without using regex to parse regex + funcs = set(each['match'].split('\\b(')[1].split('\n')[0].split('|')) + if 'name' in each and each['name'] == 'support.variable.meson': + # \\b(builtin1|builtin2...)\\b + builtin = set(each['match'].split('\\b(')[1].split(')\\b')[0].split('|')) + self.assertEqual(builtin, set(interp.builtin.keys())) + self.assertEqual(funcs, set(interp.funcs.keys())) + def test_all_functions_defined_in_ast_interpreter(self): ''' Ensure that the all functions defined in the Interpreter are also defined @@ -2369,13 +2410,21 @@ int main(int argc, char **argv) { return 0; } ''') + xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') + xz_checksumfile = xz_distfile + '.sha256sum' + zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip') + zip_checksumfile = zip_distfile + '.sha256sum' vcs_init(project_dir) self.init(project_dir) self.build('dist') - distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') - checksumfile = distfile + '.sha256sum' - self.assertPathExists(distfile) - self.assertPathExists(checksumfile) + self.assertPathExists(xz_distfile) + self.assertPathExists(xz_checksumfile) + self.assertPathDoesNotExist(zip_distfile) + self.assertPathDoesNotExist(zip_checksumfile) + self._run(self.meson_command + ['dist', '--formats', 'zip'], + workdir=self.builddir) + self.assertPathExists(zip_distfile) + self.assertPathExists(zip_checksumfile) def test_rpath_uses_ORIGIN(self): ''' @@ -2630,6 +2679,7 @@ int main(int argc, char **argv) { 'section': 'user', 'type': 'array', 'value': ['foo', 'bar'], + 'machine': 'any', } tdir = os.path.join(self.unit_test_dir, '19 array option') self.init(tdir) @@ -2655,6 +2705,7 @@ int main(int argc, char **argv) { 'section': 'user', 'type': 'array', 'value': ['foo', 'bar'], + 'machine': 'any', } tdir = os.path.join(self.unit_test_dir, '19 array option') self.init(tdir) @@ -2680,6 +2731,7 @@ int main(int argc, char **argv) { 'section': 'user', 'type': 'array', 'value': [], + 'machine': 'any', } tdir = os.path.join(self.unit_test_dir, '19 array option') self.init(tdir, extra_args='-Dlist=') @@ -3496,6 +3548,7 @@ recommended as it is not supported on some platforms''') ('section', str), ('type', str), ('description', str), + ('machine', str), ] buildoptions_typelist = [ @@ -3506,6 +3559,9 @@ recommended as it is not supported on some platforms''') ('array', list, []), ] + buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test'] + buildoptions_machines = ['any', 'build', 'host'] + dependencies_typelist = [ ('name', str), ('compile_args', list), @@ -3561,6 +3617,8 @@ recommended as it is not supported on some platforms''') valid_type = True break + self.assertIn(i['section'], buildoptions_sections) + self.assertIn(i['machine'], buildoptions_machines) self.assertTrue(valid_type) if i['name'] in buildopts_to_find: self.assertEqual(i['value'], buildopts_to_find[i['name']]) @@ -6547,17 +6605,32 @@ def unset_envs(): if v in os.environ: del os.environ[v] +def convert_args(argv): + # If we got passed a list of tests, pass it on + pytest_args = ['-v'] if '-v' in argv else [] + test_list = [] + for arg in argv: + if arg.startswith('-'): + continue + # ClassName.test_name => 'ClassName and test_name' + if '.' in arg: + arg = ' and '.join(arg.split('.')) + test_list.append(arg) + if test_list: + pytest_args += ['-k', ' or '.join(test_list)] + return pytest_args + def main(): unset_envs() - pytest_args = ['-n', 'auto', './run_unittests.py'] - if shutil.which('pytest-3'): - return subprocess.run(['pytest-3'] + pytest_args).returncode - elif shutil.which('pytest'): - return subprocess.run(['pytest'] + pytest_args).returncode try: import pytest # noqa: F401 + # Need pytest-xdist for `-n` arg + import xdist # noqa: F401 + pytest_args = ['-n', 'auto', './run_unittests.py'] + pytest_args += convert_args(sys.argv[1:]) return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode except ImportError: + print('pytest-xdist not found, using unittest instead') pass # All attempts at locating pytest failed, fall back to plain unittest. cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', @@ -49,6 +49,7 @@ if sys.platform != 'win32': if __name__ == '__main__': setup(name='meson', version=version, + extras_require={'progress': ['tqdm']}, packages=packages, package_data=package_data, entry_points=entries, diff --git a/test cases/cmake/10 cmake_module_path/cmake/FindSomethingLikePython.cmake b/test cases/cmake/10 cmake_module_path/cmake/FindSomethingLikePython.cmake new file mode 100644 index 0000000..4a189bf --- /dev/null +++ b/test cases/cmake/10 cmake_module_path/cmake/FindSomethingLikePython.cmake @@ -0,0 +1,24 @@ +cmake_policy(VERSION 3.7) + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) + find_package(Python COMPONENTS Interpreter) +else() + find_package(PythonInterp) +endif() + +if(Python_FOUND OR PYTHONINTERP_FOUND) + set(SomethingLikePython_FOUND ON) + set(SomethingLikePython_EXECUTABLE ${Python_EXECUTABLE}) + + if(NOT DEFINED Python_VERSION) + set(Python_VERSION ${Python_VERSION_STRING}) + endif() + if(NOT TARGET Python::Interpreter) + add_executable(Python::Interpreter IMPORTED) + set_target_properties(Python::Interpreter PROPERTIES + IMPORTED_LOCATION ${Python_EXECUTABLE} + VERSION ${Python_VERSION}) + endif() +else() + set(SomethingLikePython_FOUND OFF) +endif() diff --git a/test cases/cmake/10 cmake_module_path/meson.build b/test cases/cmake/10 cmake_module_path/meson.build new file mode 100644 index 0000000..2259268 --- /dev/null +++ b/test cases/cmake/10 cmake_module_path/meson.build @@ -0,0 +1,17 @@ +# We use Python3 as it's the only thing guaranteed to be available on any platform Meson can run on (unlike Zlib in linuxlike/13 cmake dependency). + +project('user CMake find_package module using cmake_module_path', + meson_version: '>= 0.50.0') + +if not find_program('cmake', required: false).found() + error('MESON_SKIP_TEST cmake binary not available.') +endif + +# NOTE: can't request Python3 via dependency('Python3', method: 'cmake') +# Meson intercepts and wants "method: auto" + +# Try to find a dependency with a custom CMake module + +dependency('SomethingLikePython', required : true, method : 'cmake', cmake_module_path : 'cmake', modules: 'Python::Interpreter') + +dependency('SomethingLikePython', method : 'cmake', cmake_module_path : ['doesNotExist', 'cmake'], modules: 'Python::Interpreter') diff --git a/test cases/cmake/10 generator expressions/main.cpp b/test cases/cmake/10 generator expressions/main.cpp new file mode 100644 index 0000000..315c0f7 --- /dev/null +++ b/test cases/cmake/10 generator expressions/main.cpp @@ -0,0 +1,10 @@ +#include <iostream> +#include <cmMod.hpp> + +using namespace std; + +int main() { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/10 generator expressions/meson.build b/test cases/cmake/10 generator expressions/meson.build new file mode 100644 index 0000000..ca08a3f --- /dev/null +++ b/test cases/cmake/10 generator expressions/meson.build @@ -0,0 +1,12 @@ +project('cmakeSubTest', ['c', 'cpp']) + +cm = import('cmake') + +sub_pro = cm.subproject('cmMod') +sub_dep = sub_pro.dependency('cmModLib') + +assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target') +assert(sub_pro.target_type('cmModLib') == 'header_only', 'Target type should be header_only') + +exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) +test('test1', exe1) diff --git a/test cases/cmake/10 generator expressions/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/10 generator expressions/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..dc4f9e4 --- /dev/null +++ b/test cases/cmake/10 generator expressions/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set (CMAKE_CXX_STANDARD 14) + +include(GNUInstallDirs) + +add_library(cmModLib INTERFACE) + +target_compile_options(cmModLib + INTERFACE $<$<AND:$<CONFIG:Release>,$<CONFIG:Debug>>:-DCMAKE_FLAG_ERROR_A> # Check discard = false + INTERFACE "-DCMAKE_FLAG_REQUIRED_A" + INTERFACE $<$<AND:1,$<STREQUAL:asd,$<LOWER_CASE:AsD>>,$<NOT:$<EQUAL:4,2>>>:-DCMAKE_FLAG_REQUIRED_B> + INTERFACE $<$<VERSION_LESS:1.2.3,2.1.0>:-DCMAKE_FLAG_REQUIRED_C> +) + +target_include_directories(cmModLib INTERFACE + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> +) + +target_compile_definitions(cmModLib INTERFACE -DCMAKE_COMPILER_DEFINE_STR="compDef") diff --git a/test cases/cmake/10 generator expressions/subprojects/cmMod/include/cmMod.hpp b/test cases/cmake/10 generator expressions/subprojects/cmMod/include/cmMod.hpp new file mode 100644 index 0000000..1f00107 --- /dev/null +++ b/test cases/cmake/10 generator expressions/subprojects/cmMod/include/cmMod.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include <string> + +#ifndef CMAKE_FLAG_REQUIRED_A +#error "The flag CMAKE_FLAG_REQUIRED_A was not set" +#endif + +#ifndef CMAKE_FLAG_REQUIRED_B +#error "The flag CMAKE_FLAG_REQUIRED_B was not set" +#endif + +#ifndef CMAKE_FLAG_REQUIRED_C +#error "The flag CMAKE_FLAG_REQUIRED_C was not set" +#endif + +#ifdef CMAKE_FLAG_ERROR_A +#error "The flag CMAKE_FLAG_ERROR_A was set" +#endif + +class cmModClass { + private: + std::string str; + public: + cmModClass(std::string foo) { + str = foo + " World "; + str += CMAKE_COMPILER_DEFINE_STR; + } + + inline std::string getStr() const { return str; } +}; diff --git a/test cases/cmake/9 header only/main.cpp b/test cases/cmake/9 header only/main.cpp new file mode 100644 index 0000000..315c0f7 --- /dev/null +++ b/test cases/cmake/9 header only/main.cpp @@ -0,0 +1,10 @@ +#include <iostream> +#include <cmMod.hpp> + +using namespace std; + +int main() { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/9 header only/meson.build b/test cases/cmake/9 header only/meson.build new file mode 100644 index 0000000..ca08a3f --- /dev/null +++ b/test cases/cmake/9 header only/meson.build @@ -0,0 +1,12 @@ +project('cmakeSubTest', ['c', 'cpp']) + +cm = import('cmake') + +sub_pro = cm.subproject('cmMod') +sub_dep = sub_pro.dependency('cmModLib') + +assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target') +assert(sub_pro.target_type('cmModLib') == 'header_only', 'Target type should be header_only') + +exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) +test('test1', exe1) diff --git a/test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..f5d9a47 --- /dev/null +++ b/test cases/cmake/9 header only/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set (CMAKE_CXX_STANDARD 14) + +add_definitions("-DDO_NOTHING_JUST_A_FLAG=1") + +add_library(cmModLib INTERFACE) +set_target_properties(cmModLib PROPERTIES INTERFACE_COMPILE_OPTIONS "-DCMAKE_FLAG_MUST_BE_PRESENT") +target_include_directories(cmModLib INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_compile_definitions(cmModLib INTERFACE -DCMAKE_COMPILER_DEFINE_STR="compDef") diff --git a/test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp b/test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp new file mode 100644 index 0000000..7ea72f7 --- /dev/null +++ b/test cases/cmake/9 header only/subprojects/cmMod/include/cmMod.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <string> + +#ifndef CMAKE_FLAG_MUST_BE_PRESENT +#error "The flag CMAKE_FLAG_MUST_BE_PRESENT was not set" +#endif + +class cmModClass { + private: + std::string str; + public: + cmModClass(std::string foo) { + str = foo + " World "; + str += CMAKE_COMPILER_DEFINE_STR; + } + + inline std::string getStr() const { return str; } +}; diff --git a/test cases/common/122 shared module/module.c b/test cases/common/122 shared module/module.c index 181b760..9c8774d 100644 --- a/test cases/common/122 shared module/module.c +++ b/test cases/common/122 shared module/module.c @@ -27,6 +27,19 @@ fptr find_any_f (const char *name) { #include <windows.h> #include <tlhelp32.h> +static wchar_t* +win32_get_last_error (void) +{ + wchar_t *msg = NULL; + + FormatMessageW (FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_IGNORE_INSERTS + | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, GetLastError (), 0, + (LPWSTR) &msg, 0, NULL); + return msg; +} + /* Unlike Linux and OS X, when a library is loaded, all the symbols aren't * loaded into a single namespace. You must fetch the symbol by iterating over * all loaded modules. Code for finding the function from any of the loaded @@ -38,7 +51,8 @@ fptr find_any_f (const char *name) { snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPMODULE, 0); if (snapshot == (HANDLE) -1) { - printf("Could not get snapshot\n"); + wchar_t *msg = win32_get_last_error (); + printf("Could not get snapshot: %S\n", msg); return 0; } diff --git a/test cases/common/122 shared module/prog.c b/test cases/common/122 shared module/prog.c index 2b63840..8b56d93 100644 --- a/test cases/common/122 shared module/prog.c +++ b/test cases/common/122 shared module/prog.c @@ -8,7 +8,7 @@ typedef int (*fptr) (void); #include <windows.h> -wchar_t* +static wchar_t* win32_get_last_error (void) { wchar_t *msg = NULL; diff --git a/test cases/common/13 pch/c/meson.build b/test cases/common/13 pch/c/meson.build index fe4ac68..6fba15b 100644 --- a/test cases/common/13 pch/c/meson.build +++ b/test cases/common/13 pch/c/meson.build @@ -1,8 +1,14 @@ cc = meson.get_compiler('c') cc_id = cc.get_id() + if cc_id == 'lcc' error('MESON_SKIP_TEST: Elbrus compiler does not support PCH.') endif +# PGI compiler only supports PCH for C++ +if cc_id == 'pgi' + subdir_done() +endif + exe = executable('prog', 'prog.c', c_pch : 'pch/prog.h') diff --git a/test cases/common/13 pch/cpp/prog.cc b/test cases/common/13 pch/cpp/prog.cc index 629d880..ea258c6 100644 --- a/test cases/common/13 pch/cpp/prog.cc +++ b/test cases/common/13 pch/cpp/prog.cc @@ -1,8 +1,11 @@ +// Note: if using PGI compilers, you will need to add #include "prog.hh" +// even though you're using precompiled headers. void func() { std::cout << "This is a function that fails to compile if iostream is not included." << std::endl; } int main(int argc, char **argv) { + func(); return 0; } diff --git a/test cases/common/13 pch/generated/meson.build b/test cases/common/13 pch/generated/meson.build index 1ef771b..ba06bce 100644 --- a/test cases/common/13 pch/generated/meson.build +++ b/test cases/common/13 pch/generated/meson.build @@ -1,9 +1,15 @@ cc = meson.get_compiler('c') cc_id = cc.get_id() + if cc_id == 'lcc' error('MESON_SKIP_TEST: Elbrus compiler does not support PCH.') endif +# PGI compiler only supports PCH for C++ +if cc_id == 'pgi' + subdir_done() +endif + generated_customTarget = custom_target('makeheader', output: 'generated_customTarget.h', command : [find_program('gen_custom.py'), '@OUTPUT0@']) diff --git a/test cases/common/13 pch/meson.build b/test cases/common/13 pch/meson.build index 4438c9e..334afc5 100644 --- a/test cases/common/13 pch/meson.build +++ b/test cases/common/13 pch/meson.build @@ -1,4 +1,12 @@ -project('pch test', 'c', 'cpp') +project('pch test', 'c', 'cpp', + meson_version: '>= 0.46.0') + +cc = meson.get_compiler('c') +cc_id = cc.get_id() + +if cc_id == 'pgi' + error('MESON_SKIP_TEST: PGI compiler does support PCH, however, PGI cannot tolerate spaces in the --pch_dir path and Meson run_project_tests.py uses spaces in temporary build path names. If this test is run individually with no spaces in build path, it will pass.') +endif subdir('c') subdir('cpp') diff --git a/test cases/common/13 pch/mixed/meson.build b/test cases/common/13 pch/mixed/meson.build index cbb7bac..266e7a5 100644 --- a/test cases/common/13 pch/mixed/meson.build +++ b/test cases/common/13 pch/mixed/meson.build @@ -1,3 +1,11 @@ +cc = meson.get_compiler('c') +cc_id = cc.get_id() + +# PGI compiler only supports PCH for C++ +if cc_id == 'pgi' + subdir_done() +endif + exe = executable( 'prog', files('main.cc', 'func.c'), diff --git a/test cases/common/13 pch/withIncludeDirectories/meson.build b/test cases/common/13 pch/withIncludeDirectories/meson.build index 68e544b..95f7888 100644 --- a/test cases/common/13 pch/withIncludeDirectories/meson.build +++ b/test cases/common/13 pch/withIncludeDirectories/meson.build @@ -1,9 +1,15 @@ cc = meson.get_compiler('c') cc_id = cc.get_id() + if cc_id == 'lcc' error('MESON_SKIP_TEST: Elbrus compiler does not support PCH.') endif +# PGI compiler only supports PCH for C++ +if cc_id == 'pgi' + subdir_done() +endif + exe = executable('prog', 'prog.c', include_directories: 'include', c_pch : 'pch/prog.h') diff --git a/test cases/common/164 disabler/meson.build b/test cases/common/164 disabler/meson.build index a1763d2..9437ace 100644 --- a/test cases/common/164 disabler/meson.build +++ b/test cases/common/164 disabler/meson.build @@ -33,6 +33,7 @@ assert(number == 2, 'If found handled incorrectly, value should be 2 but is @0@' dep = dependency('notfounddep', required : false, disabler : true) app = executable('myapp', 'notfound.c', dependencies : [dep]) +app = executable('myapp', 'notfound.c', dependencies : [[dep]]) cc = meson.get_compiler('c') dep = cc.find_library('notfounddep', required : false, disabler : true) diff --git a/test cases/common/190 openmp/meson.build b/test cases/common/190 openmp/meson.build index 71bf697..a1154c2 100644 --- a/test cases/common/190 openmp/meson.build +++ b/test cases/common/190 openmp/meson.build @@ -1,4 +1,4 @@ -project('openmp', 'c', 'cpp') +project('openmp', 'c') cc = meson.get_compiler('c') if cc.get_id() == 'gcc' and cc.version().version_compare('<4.2.0') @@ -21,21 +21,22 @@ if host_machine.system() == 'darwin' endif openmp = dependency('openmp') +env = environment() +env.set('OMP_NUM_THREADS', '2') exec = executable('exec', 'main.c', dependencies : [openmp]) - -execpp = executable('execpp', - 'main.cpp', - dependencies : [openmp]) - -env = environment() -env.set('OMP_NUM_THREADS', '2') - test('OpenMP C', exec, env : env) -test('OpenMP C++', execpp, env : env) +if not(build_machine.system() == 'windows' and cc.get_id() == 'pgi') + if add_languages('cpp', required : false) + execpp = executable('execpp', + 'main.cpp', + dependencies : [openmp]) + test('OpenMP C++', execpp, env : env) + endif +endif if add_languages('fortran', required : false) # Mixing compilers (msvc/clang with gfortran) does not seem to work on Windows. diff --git a/test cases/common/223 source set realistic example/meson.build b/test cases/common/223 source set realistic example/meson.build index f983e8b..2a9475a 100644 --- a/test cases/common/223 source set realistic example/meson.build +++ b/test cases/common/223 source set realistic example/meson.build @@ -2,6 +2,12 @@ # modules, inspired by QEMU's build system project('sourceset-example', 'cpp') + +cppid = meson.get_compiler('cpp').get_id() +if cppid == 'pgi' + error('MESON_SKIP_TEST: Even PGI 19.4 that claims C++17 full support, cannot handle auto x = y syntax used in this test.') +endif + ss = import('sourceset') kconfig = import('unstable-kconfig') diff --git a/test cases/common/29 find program/meson.build b/test cases/common/29 find program/meson.build index ba5386d..983b7b4 100644 --- a/test cases/common/29 find program/meson.build +++ b/test cases/common/29 find program/meson.build @@ -18,3 +18,12 @@ else e = executable('prog', generated) test('external exe', e) endif + +prog = find_program('print-version.py', version : '>=2.0', required : false) +assert(not prog.found(), 'Version should be too old') + +prog = find_program('print-version.py', version : '>=1.0') +assert(prog.found(), 'Program version should match') + +prog = find_program('print-version-with-prefix.py', version : '>=1.0') +assert(prog.found(), 'Program version should match') diff --git a/test cases/common/29 find program/print-version-with-prefix.py b/test cases/common/29 find program/print-version-with-prefix.py new file mode 100644 index 0000000..520e0ba --- /dev/null +++ b/test cases/common/29 find program/print-version-with-prefix.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import sys + +if len(sys.argv) != 2 or sys.argv[1] != '--version': + exit(1) + +print('Version: 1.0') diff --git a/test cases/common/29 find program/print-version.py b/test cases/common/29 find program/print-version.py new file mode 100644 index 0000000..4a78e5b --- /dev/null +++ b/test cases/common/29 find program/print-version.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import sys + +if len(sys.argv) != 2 or sys.argv[1] != '--version': + exit(1) + +print('1.0') diff --git a/test cases/fortran/13 coarray/meson.build b/test cases/fortran/13 coarray/meson.build index 3160aa6..3e52dde 100644 --- a/test cases/fortran/13 coarray/meson.build +++ b/test cases/fortran/13 coarray/meson.build @@ -1,6 +1,13 @@ project('Fortran coarray', 'fortran', meson_version: '>=0.50') +fc = meson.get_compiler('fortran') +fcid = fc.get_id() + +if ['pgi', 'flang'].contains(fcid) + error('MESON_SKIP_TEST: At least through PGI 19.4 and Flang 7.1 do not support Fortran Coarrays.') +endif + # coarray is required because single-image fallback is an intrinsic feature coarray = dependency('coarray', required : true) diff --git a/test cases/fortran/14 fortran links c/meson.build b/test cases/fortran/14 fortran links c/meson.build index 1ac47e4..a45f06f 100644 --- a/test cases/fortran/14 fortran links c/meson.build +++ b/test cases/fortran/14 fortran links c/meson.build @@ -1,5 +1,6 @@ project('Fortran calling C', 'fortran', 'c', - meson_version: '>= 0.51.0') + meson_version: '>= 0.51.0', + default_options : ['default_library=static']) ccid = meson.get_compiler('c').get_id() if ccid == 'msvc' or ccid == 'clang-cl' diff --git a/test cases/fortran/2 modules/meson.build b/test cases/fortran/2 modules/meson.build index fb58b9d..791ae63 100644 --- a/test cases/fortran/2 modules/meson.build +++ b/test cases/fortran/2 modules/meson.build @@ -1,4 +1,5 @@ -project('modules', 'fortran') +project('modules', 'fortran', + default_options : ['default_library=static']) commented = library('commented', 'comment_mod.f90') diff --git a/test cases/fortran/6 dynamic/meson.build b/test cases/fortran/6 dynamic/meson.build index 244a38b..413223b 100644 --- a/test cases/fortran/6 dynamic/meson.build +++ b/test cases/fortran/6 dynamic/meson.build @@ -1,9 +1,11 @@ project('dynamic_fortran', 'fortran') -if meson.get_compiler('fortran').get_id() == 'intel-cl' - error('MESON_SKIP_TEST: Windows ifort does not use shared_library in a sane way') +fcid = meson.get_compiler('fortran').get_id() +if fcid == 'intel-cl' or (host_machine.system() == 'windows' and fcid == 'pgi') + error('MESON_SKIP_TEST: non-Gfortran Windows Fortran compilers do not do shared libraries in a Fortran standard way') # !DEC$ ATTRIBUTES DLLEXPORT must be used! # https://software.intel.com/en-us/node/535306 + # https://www.pgroup.com/resources/docs/19.4/x86/pgi-user-guide/index.htm#lib-dynlnk-bld-dll-fort endif dynamic = shared_library('dynamic', 'dynamic.f90') diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar1/foobar-docs.sgml b/test cases/frameworks/10 gtk-doc/doc/foobar1/foobar-docs.sgml new file mode 100644 index 0000000..95f73ef --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar1/foobar-docs.sgml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ +<!ENTITY version SYSTEM "../version.xml"> +]> +<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude"> + <bookinfo> + <title>Foolib Reference Manual</title> + <releaseinfo> + for Foobar &version; + </releaseinfo> + <authorgroup> + <author> + <firstname>Jonny</firstname> + <surname>Example</surname> + <affiliation> + <address> + <email>unknown@example.com</email> + </address> + </affiliation> + </author> + </authorgroup> + <copyright> + <year>2015</year> + <holder>Foobar corporation holdings ltd</holder> + </copyright> + </bookinfo> + + <reference id="foobar"> + <title>Foobar library</title> + <partintro> + <para> + This part documents Foobar libs. + </para> + </partintro> + <xi:include href="xml/foo.xml"/> + <xi:include href="../../include/bar.xml"/> + <xi:include href="xml/foo-version.xml"/> + </reference> + +</book> diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar1/meson.build b/test cases/frameworks/10 gtk-doc/doc/foobar1/meson.build new file mode 100644 index 0000000..149c6e9 --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar1/meson.build @@ -0,0 +1,5 @@ +gnome.gtkdoc('foobar', + src_dir : inc, + main_sgml : 'foobar-docs.sgml', + content_files : [docbook, version_xml], + install : true) diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar2/foobar-docs.sgml b/test cases/frameworks/10 gtk-doc/doc/foobar2/foobar-docs.sgml new file mode 100644 index 0000000..95f73ef --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar2/foobar-docs.sgml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ +<!ENTITY version SYSTEM "../version.xml"> +]> +<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude"> + <bookinfo> + <title>Foolib Reference Manual</title> + <releaseinfo> + for Foobar &version; + </releaseinfo> + <authorgroup> + <author> + <firstname>Jonny</firstname> + <surname>Example</surname> + <affiliation> + <address> + <email>unknown@example.com</email> + </address> + </affiliation> + </author> + </authorgroup> + <copyright> + <year>2015</year> + <holder>Foobar corporation holdings ltd</holder> + </copyright> + </bookinfo> + + <reference id="foobar"> + <title>Foobar library</title> + <partintro> + <para> + This part documents Foobar libs. + </para> + </partintro> + <xi:include href="xml/foo.xml"/> + <xi:include href="../../include/bar.xml"/> + <xi:include href="xml/foo-version.xml"/> + </reference> + +</book> diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar2/meson.build b/test cases/frameworks/10 gtk-doc/doc/foobar2/meson.build new file mode 100644 index 0000000..0b2faa0 --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar2/meson.build @@ -0,0 +1,6 @@ +gnome.gtkdoc('foobar2', + src_dir : inc, + main_sgml : 'foobar-docs.sgml', + content_files : [docbook, version_xml], + install : true, + install_dir : 'foobar2') diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar3/foobar-docs.sgml b/test cases/frameworks/10 gtk-doc/doc/foobar3/foobar-docs.sgml new file mode 100644 index 0000000..95f73ef --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar3/foobar-docs.sgml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ +<!ENTITY version SYSTEM "../version.xml"> +]> +<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude"> + <bookinfo> + <title>Foolib Reference Manual</title> + <releaseinfo> + for Foobar &version; + </releaseinfo> + <authorgroup> + <author> + <firstname>Jonny</firstname> + <surname>Example</surname> + <affiliation> + <address> + <email>unknown@example.com</email> + </address> + </affiliation> + </author> + </authorgroup> + <copyright> + <year>2015</year> + <holder>Foobar corporation holdings ltd</holder> + </copyright> + </bookinfo> + + <reference id="foobar"> + <title>Foobar library</title> + <partintro> + <para> + This part documents Foobar libs. + </para> + </partintro> + <xi:include href="xml/foo.xml"/> + <xi:include href="../../include/bar.xml"/> + <xi:include href="xml/foo-version.xml"/> + </reference> + +</book> diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar3/meson.build b/test cases/frameworks/10 gtk-doc/doc/foobar3/meson.build new file mode 100644 index 0000000..0dce2f8 --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar3/meson.build @@ -0,0 +1,6 @@ +gnome.gtkdoc('foobar', + module_version : '3.0', + src_dir : inc, + main_sgml : 'foobar-docs.sgml', + content_files : [docbook, version_xml], + install : true) diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar4/foobar-docs.sgml b/test cases/frameworks/10 gtk-doc/doc/foobar4/foobar-docs.sgml new file mode 100644 index 0000000..95f73ef --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar4/foobar-docs.sgml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ +<!ENTITY version SYSTEM "../version.xml"> +]> +<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude"> + <bookinfo> + <title>Foolib Reference Manual</title> + <releaseinfo> + for Foobar &version; + </releaseinfo> + <authorgroup> + <author> + <firstname>Jonny</firstname> + <surname>Example</surname> + <affiliation> + <address> + <email>unknown@example.com</email> + </address> + </affiliation> + </author> + </authorgroup> + <copyright> + <year>2015</year> + <holder>Foobar corporation holdings ltd</holder> + </copyright> + </bookinfo> + + <reference id="foobar"> + <title>Foobar library</title> + <partintro> + <para> + This part documents Foobar libs. + </para> + </partintro> + <xi:include href="xml/foo.xml"/> + <xi:include href="../../include/bar.xml"/> + <xi:include href="xml/foo-version.xml"/> + </reference> + +</book> diff --git a/test cases/frameworks/10 gtk-doc/doc/foobar4/meson.build b/test cases/frameworks/10 gtk-doc/doc/foobar4/meson.build new file mode 100644 index 0000000..959e507 --- /dev/null +++ b/test cases/frameworks/10 gtk-doc/doc/foobar4/meson.build @@ -0,0 +1,7 @@ +gnome.gtkdoc('foobar2', + module_version : '3.0', + src_dir : inc, + main_sgml : 'foobar-docs.sgml', + content_files : [docbook, version_xml], + install : true, + install_dir : 'foobar3') diff --git a/test cases/frameworks/10 gtk-doc/doc/meson.build b/test cases/frameworks/10 gtk-doc/doc/meson.build index 019be94..c001f89 100644 --- a/test cases/frameworks/10 gtk-doc/doc/meson.build +++ b/test cases/frameworks/10 gtk-doc/doc/meson.build @@ -4,30 +4,7 @@ version_xml = configure_file(input : 'version.xml.in', output : 'version.xml', configuration : cdata) -gnome.gtkdoc('foobar', - src_dir : inc, - main_sgml : 'foobar-docs.sgml', - content_files : [docbook, version_xml], - install : true) - -gnome.gtkdoc('foobar2', - src_dir : inc, - main_sgml : 'foobar-docs.sgml', - content_files : [docbook, version_xml], - install : true, - install_dir : 'foobar2') - -gnome.gtkdoc('foobar', - module_version : '3.0', - src_dir : inc, - main_sgml : 'foobar-docs.sgml', - content_files : [docbook, version_xml], - install : true) - -gnome.gtkdoc('foobar2', - module_version : '3.0', - src_dir : inc, - main_sgml : 'foobar-docs.sgml', - content_files : [docbook, version_xml], - install : true, - install_dir : 'foobar3') +subdir('foobar1') +subdir('foobar2') +subdir('foobar3') +subdir('foobar4') diff --git a/test cases/kconfig/4 load_config builddir/meson.build b/test cases/kconfig/4 load_config builddir/meson.build index 93136ba..1924d23 100644 --- a/test cases/kconfig/4 load_config builddir/meson.build +++ b/test cases/kconfig/4 load_config builddir/meson.build @@ -2,8 +2,8 @@ project('kconfig builddir test') k = import('unstable-kconfig') -configure_file(input: 'config', output: 'out-config', copy: true) -conf = k.load(meson.build_root() / 'out-config') +out_conf = configure_file(input: 'config', output: 'out-config', copy: true) +conf = k.load(out_conf) if not conf.has_key('CONFIG_IS_SET') error('Expected CONFIG_IS_SET to be set, but it wasn\'t') diff --git a/test cases/linuxlike/13 cmake dependency/cmake/FindImportedTarget.cmake b/test cases/linuxlike/13 cmake dependency/cmake/FindImportedTarget.cmake new file mode 100644 index 0000000..d65c6fb --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/cmake/FindImportedTarget.cmake @@ -0,0 +1,13 @@ +find_package(ZLIB) + +if(ZLIB_FOUND OR ZLIB_Found) + set(ImportedTarget_FOUND ON) + add_library(mesonTestLibDefs UNKNOWN IMPORTED) + set_property(TARGET mesonTestLibDefs PROPERTY IMPORTED_LOCATION ${ZLIB_LIBRARY}) + set_property(TARGET mesonTestLibDefs PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${ZLIB_INCLUDE_DIR}) + set_property(TARGET mesonTestLibDefs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS REQUIRED_MESON_FLAG1) + set_property(TARGET mesonTestLibDefs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS $<$<NOT:$<CONFIG:Debug>>:QT_NO_DEBUG>) # Error empty string + set_property(TARGET mesonTestLibDefs APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS REQUIRED_MESON_FLAG2) +else() + set(ImportedTarget_FOUND OFF) +endif() diff --git a/test cases/linuxlike/13 cmake dependency/cmake_pref_env/lib/cmake/cmMesonTestDep/cmMesonTestDepConfig.cmake b/test cases/linuxlike/13 cmake dependency/cmake_pref_env/lib/cmake/cmMesonTestDep/cmMesonTestDepConfig.cmake new file mode 100644 index 0000000..06a2060 --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/cmake_pref_env/lib/cmake/cmMesonTestDep/cmMesonTestDepConfig.cmake @@ -0,0 +1,9 @@ +find_package(ZLIB) + +if(ZLIB_FOUND OR ZLIB_Found) + set(cmMesonTestDep_FOUND ON) + set(cmMesonTestDep_LIBRARIES ${ZLIB_LIBRARY}) + set(cmMesonTestDep_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR}) +else() + set(cmMesonTestDep_FOUND OFF) +endif() diff --git a/test cases/linuxlike/13 cmake dependency/meson.build b/test cases/linuxlike/13 cmake dependency/meson.build index a18cd84..411b7a3 100644 --- a/test cases/linuxlike/13 cmake dependency/meson.build +++ b/test cases/linuxlike/13 cmake dependency/meson.build @@ -36,12 +36,22 @@ depf2 = dependency('ZLIB', required : false, method : 'cmake', modules : 'dfggh: assert(depf2.found() == false, 'Invalid CMake targets should fail') +# Try to find cmMesonTestDep in a custom prefix + +depPrefEnv = dependency('cmMesonTestDep', required : true, method : 'cmake') + # Try to find a dependency with a custom CMake module depm1 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake_module_path : 'cmake') depm2 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake_module_path : ['cmake']) depm3 = dependency('SomethingLikeZLIB', required : true, cmake_module_path : 'cmake') +# Test some edge cases with spaces, etc. + +testDep = dependency('ImportedTarget', required : true, method : 'cmake', cmake_module_path : 'cmake', modules: 'mesonTestLibDefs') +testFlagSet = executable('testFlagSet', ['testFlagSet.c'], dependencies: [testDep]) +test('testFlagSetTest', testFlagSet) + # Try to compile a test that takes a dep and an include_directories cc = meson.get_compiler('c') diff --git a/test cases/linuxlike/13 cmake dependency/setup_env.json b/test cases/linuxlike/13 cmake dependency/setup_env.json new file mode 100644 index 0000000..aa15374 --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/setup_env.json @@ -0,0 +1,3 @@ +{ + "CMAKE_PREFIX_PATH": "@ROOT@/cmake_pref_env" +} diff --git a/test cases/linuxlike/13 cmake dependency/testFlagSet.c b/test cases/linuxlike/13 cmake dependency/testFlagSet.c new file mode 100644 index 0000000..0c92690 --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/testFlagSet.c @@ -0,0 +1,18 @@ +#include<stdio.h> +#include<zlib.h> + +#ifndef REQUIRED_MESON_FLAG1 +#error "REQUIRED_MESON_FLAG1 not set" +#endif + +#ifndef REQUIRED_MESON_FLAG2 +#error "REQUIRED_MESON_FLAG2 not set" +#endif + +int main(int argc, char *argv[]) { + printf("Hello World\n"); + void * something = deflate; + if(something != 0) + return 0; + return 1; +} diff --git a/test cases/unit/55 introspection/meson.build b/test cases/unit/55 introspection/meson.build index 3f013aa..7589f3f 100644 --- a/test cases/unit/55 introspection/meson.build +++ b/test cases/unit/55 introspection/meson.build @@ -25,7 +25,7 @@ var2 = 2.to_string() var3 = 'test3' t1 = executable('test' + var1, ['t1.cpp'], link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2')) -t2 = executable('test@0@'.format('@0@'.format(var2)), 't2.cpp', link_with: [staticlib]) +t2 = executable('test@0@'.format('@0@'.format(var2)), sources: ['t2.cpp'], link_with: [staticlib]) t3 = executable(var3, 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1]) ### BEGIN: Test inspired by taisei: https://github.com/taisei-project/taisei/blob/master/meson.build#L293 diff --git a/test cases/windows/9 vs module defs generated/subdir/meson.build b/test cases/windows/9 vs module defs generated/subdir/meson.build index 5d390a0..356f83a 100644 --- a/test cases/windows/9 vs module defs generated/subdir/meson.build +++ b/test cases/windows/9 vs module defs generated/subdir/meson.build @@ -7,3 +7,4 @@ def_file = configure_file( ) shlib = shared_library('somedll', 'somedll.c', vs_module_defs : def_file) +shmod = shared_module('somemod', 'somedll.c', vs_module_defs : def_file) |