diff options
302 files changed, 4245 insertions, 978 deletions
diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index bde2223..0cf9156 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -31,6 +31,7 @@ jobs: - { name: CUDA (on Arch), id: cuda } - { name: Fedora, id: fedora } - { name: OpenSUSE, id: opensuse } + - { name: Ubuntu Bionic, id: bionic } - { name: Ubuntu Eoan, id: eoan } steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/lint_mypy.yml b/.github/workflows/lint_mypy.yml index c826729..7afee2e 100644 --- a/.github/workflows/lint_mypy.yml +++ b/.github/workflows/lint_mypy.yml @@ -19,7 +19,8 @@ jobs: - uses: actions/setup-python@v1 with: python-version: '3.x' - - run: python -m pip install pylint + # pylint version constraint can be removed when https://github.com/PyCQA/pylint/issues/3524 is resolved + - run: python -m pip install pylint==2.4.4 - run: pylint mesonbuild mypy: diff --git a/.github/workflows/os_comp.yml b/.github/workflows/os_comp.yml index 7f3437e..a5abf7d 100644 --- a/.github/workflows/os_comp.yml +++ b/.github/workflows/os_comp.yml @@ -11,7 +11,7 @@ jobs: name: Ubuntu 16.04 runs-on: ubuntu-16.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install Dependencies run: | sudo apt update -yq @@ -39,9 +39,10 @@ jobs: - { name: CUDA (on Arch), id: cuda } - { name: Fedora, id: fedora } - { name: OpenSUSE, id: opensuse } + - { name: Ubuntu Bionic, id: bionic } container: mesonbuild/${{ matrix.cfg.id }}:latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Run tests # All environment variables are stored inside the docker image in /ci/env_vars.sh # They are defined in the `env` section in each image.json. CI_ARGS should be set diff --git a/ci/ciimage/arch/install.sh b/ci/ciimage/arch/install.sh index 7fe139e..6cbbb27 100755 --- a/ci/ciimage/arch/install.sh +++ b/ci/ciimage/arch/install.sh @@ -12,7 +12,7 @@ pkgs=( itstool gtk3 java-environment=8 gtk-doc llvm clang sdl2 graphviz doxygen vulkan-validation-layers openssh mercurial gtk-sharp-2 qt5-tools libwmf valgrind cmake netcdf-fortran openmpi nasm gnustep-base gettext - python-jsonschema + python-jsonschema python-lxml # cuda ) diff --git a/ci/ciimage/bionic/image.json b/ci/ciimage/bionic/image.json new file mode 100644 index 0000000..6a3d723 --- /dev/null +++ b/ci/ciimage/bionic/image.json @@ -0,0 +1,8 @@ +{ + "base_image": "ubuntu:bionic", + "env": { + "CI": "1", + "SKIP_SCIENTIFIC": "1", + "DC": "gdc" + } +} diff --git a/ci/ciimage/bionic/install.sh b/ci/ciimage/bionic/install.sh new file mode 100755 index 0000000..47deb2a --- /dev/null +++ b/ci/ciimage/bionic/install.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +export DEBIAN_FRONTEND=noninteractive +export LANG='C.UTF-8' +export DC=gdc + +pkgs=( + python3-pytest-xdist + python3-pip libxml2-dev libxslt1-dev libyaml-dev libjson-glib-dev + wget unzip cmake doxygen + clang + pkg-config-arm-linux-gnueabihf + qt4-linguist-tools qt5-default qtbase5-private-dev + python-dev + libomp-dev + ldc + libclang-dev + libgcrypt20-dev + libgpgme-dev + libhdf5-dev openssh-server + libboost-python-dev libboost-regex-dev + libblocksruntime-dev + libperl-dev libscalapack-mpi-dev libncurses-dev +) + +boost_pkgs=(atomic chrono date-time filesystem log regex serialization system test thread) + +sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" +apt-get -y update +apt-get -y upgrade +apt-get -y install eatmydata + +# Base stuff +eatmydata apt-get -y build-dep meson + +# Add boost packages +for i in "${boost_pkgs[@]}"; do + for j in "1.62.0" "1.65.1"; do + pkgs+=("libboost-${i}${j}") + done +done + +# packages +eatmydata apt-get -y install "${pkgs[@]}" + +eatmydata python3 -m pip install codecov jsonschema + +# Install the ninja 0.10 +wget https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-linux.zip +unzip ninja-linux.zip -d /ci + +# cleanup +apt-get -y remove ninja-build +apt-get -y clean +apt-get -y autoclean +rm ninja-linux.zip diff --git a/ci/ciimage/build.py b/ci/ciimage/build.py index 34a92fa..e623a7e 100755 --- a/ci/ciimage/build.py +++ b/ci/ciimage/build.py @@ -71,6 +71,9 @@ class Builder(BuilderBase): for key, val in self.image_def.env.items(): out_data += f'export {key}="{val}"\n' + # Also add /ci to PATH + out_data += 'export PATH="/ci:$PATH"\n' + out_file.write_text(out_data) # make it executable @@ -157,7 +160,7 @@ class ImageTester(BuilderBase): test_cmd = [ self.docker, 'run', '--rm', '-t', 'meson_test_image', - '/usr/bin/bash', '-c', 'source /ci/env_vars.sh; cd meson; ./run_tests.py $CI_ARGS' + '/bin/bash', '-c', 'source /ci/env_vars.sh; cd meson; ./run_tests.py $CI_ARGS' ] if subprocess.run(test_cmd).returncode != 0: raise RuntimeError('Running tests failed') diff --git a/ci/ciimage/eoan/install.sh b/ci/ciimage/eoan/install.sh index 4b3b746..7d7a1fd 100755 --- a/ci/ciimage/eoan/install.sh +++ b/ci/ciimage/eoan/install.sh @@ -11,6 +11,7 @@ export DC=gdc pkgs=( python3-pytest-xdist python3-pip libxml2-dev libxslt1-dev libyaml-dev libjson-glib-dev + python3-lxml wget unzip qt5-default clang pkg-config-arm-linux-gnueabihf diff --git a/ci/ciimage/fedora/install.sh b/ci/ciimage/fedora/install.sh index 242d677..f61d97e 100755 --- a/ci/ciimage/fedora/install.sh +++ b/ci/ciimage/fedora/install.sh @@ -13,7 +13,7 @@ pkgs=( doxygen vulkan-devel vulkan-validation-layers-devel openssh mercurial gtk-sharp2-devel libpcap-devel gpgme-devel qt5-qtbase-devel qt5-qttools-devel qt5-linguist qt5-qtbase-private-devel libwmf-devel valgrind cmake openmpi-devel nasm gnustep-base-devel gettext-devel ncurses-devel - libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel + libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel python3-lxml ) # Sys update diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh index c5dd6df..b9e440d 100755 --- a/ci/ciimage/opensuse/install.sh +++ b/ci/ciimage/opensuse/install.sh @@ -5,7 +5,7 @@ set -e source /ci/common.sh pkgs=( - python3-setuptools python3-wheel python3-pip python3-pytest-xdist python3 + python3-setuptools python3-wheel python3-pip python3-pytest-xdist python3 python3-lxml ninja make git autoconf automake patch python3-Cython python3-jsonschema elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl mono-core gtkmm3-devel gtest gmock protobuf-devel wxGTK3-3_2-devel gobject-introspection-devel @@ -17,7 +17,7 @@ pkgs=( libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_system-devel libboost_test-devel libboost_log-devel libboost_regex-devel - libboost_python-devel libboost_python-py3-1_71_0-devel libboost_regex-devel + libboost_python3-devel libboost_regex-devel ) # Sys update diff --git a/ci/travis_install.sh b/ci/travis_install.sh index 5d191f1..d9d308a 100755 --- a/ci/travis_install.sh +++ b/ci/travis_install.sh @@ -7,9 +7,11 @@ msg() { echo -e "\x1b[1;32mINFO: \x1b[37m$*\x1b[0m"; } if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then msg "Running OSX setup" brew update + # Run one macOS build with pkg-config available (pulled in by qt), and the + # other (unity=on) without pkg-config brew install qt ldc llvm ninja if [[ "$MESON_ARGS" =~ .*unity=on.* ]]; then - which pkg-config || brew install pkg-config + which pkg-config && rm -f $(which pkg-config) fi python3 -m pip install jsonschema elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then diff --git a/data/schema.xsd b/data/schema.xsd new file mode 100644 index 0000000..58c6bfd --- /dev/null +++ b/data/schema.xsd @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- from https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-4.xsd --> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + <xs:element name="failure"> + <xs:complexType mixed="true"> + <xs:attribute name="type" type="xs:string" use="optional"/> + <xs:attribute name="message" type="xs:string" use="optional"/> + </xs:complexType> + </xs:element> + + <xs:element name="error"> + <xs:complexType mixed="true"> + <xs:attribute name="type" type="xs:string" use="optional"/> + <xs:attribute name="message" type="xs:string" use="optional"/> + </xs:complexType> + </xs:element> + + <xs:element name="properties"> + <xs:complexType> + <xs:sequence> + <xs:element ref="property" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="property"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="value" type="xs:string" use="required"/> + </xs:complexType> + </xs:element> + + <xs:element name="skipped"> + <xs:complexType mixed="true"> + <xs:attribute name="message" type="xs:string" use="optional"/> + </xs:complexType> + </xs:element> + + <xs:element name="system-err" type="xs:string"/> + <xs:element name="system-out" type="xs:string"/> + + <xs:element name="testcase"> + <xs:complexType> + <xs:sequence> + <xs:element ref="skipped" minOccurs="0" maxOccurs="1"/> + <xs:element ref="error" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="failure" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="system-out" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="system-err" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="assertions" type="xs:string" use="optional"/> + <xs:attribute name="time" type="xs:string" use="optional"/> + <xs:attribute name="classname" type="xs:string" use="optional"/> + <xs:attribute name="status" type="xs:string" use="optional"/> + </xs:complexType> + </xs:element> + + <xs:element name="testsuite"> + <xs:complexType> + <xs:sequence> + <xs:element ref="properties" minOccurs="0" maxOccurs="1"/> + <xs:element ref="testcase" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="system-out" minOccurs="0" maxOccurs="1"/> + <xs:element ref="system-err" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="tests" type="xs:string" use="required"/> + <xs:attribute name="failures" type="xs:string" use="optional"/> + <xs:attribute name="errors" type="xs:string" use="optional"/> + <xs:attribute name="time" type="xs:string" use="optional"/> + <xs:attribute name="disabled" type="xs:string" use="optional"/> + <xs:attribute name="skipped" type="xs:string" use="optional"/> + <xs:attribute name="timestamp" type="xs:string" use="optional"/> + <xs:attribute name="hostname" type="xs:string" use="optional"/> + <xs:attribute name="id" type="xs:string" use="optional"/> + <xs:attribute name="package" type="xs:string" use="optional"/> + </xs:complexType> + </xs:element> + + <xs:element name="testsuites"> + <xs:complexType> + <xs:sequence> + <xs:element ref="testsuite" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="optional"/> + <xs:attribute name="time" type="xs:string" use="optional"/> + <xs:attribute name="tests" type="xs:string" use="optional"/> + <xs:attribute name="failures" type="xs:string" use="optional"/> + <xs:attribute name="disabled" type="xs:string" use="optional"/> + <xs:attribute name="errors" type="xs:string" use="optional"/> + </xs:complexType> + </xs:element> + +</xs:schema> diff --git a/data/test.schema.json b/data/test.schema.json index 72f160f..d3b80d0 100644 --- a/data/test.schema.json +++ b/data/test.schema.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": false, "properties": { "env": { "type": "object", @@ -100,6 +101,30 @@ "prefix" ] } + }, + "tools": { + "type": "object" + }, + "stdout": { + "type": "array", + "items": { + "type": "object", + "properties": { + "line": { + "type": "string" + }, + "match": { + "type": "string", + "enum": [ + "literal", + "re" + ] + } + }, + "required": [ + "line" + ] + } } } } diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md index 2d53e28..429b9b2 100644 --- a/docs/markdown/Build-options.md +++ b/docs/markdown/Build-options.md @@ -20,6 +20,9 @@ option('integer_opt', type : 'integer', min : 0, max : 5, value : 3) # Since 0.4 option('free_array_opt', type : 'array', value : ['one', 'two']) # Since 0.44.0 option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two']) option('some_feature', type : 'feature', value : 'enabled') # Since 0.47.0 +option('long_desc', type : 'string', value : 'optval', + description : 'An option with a very long description' + + 'that does something in a specific context') # Since 0.55.0 ``` For built-in options, see [Built-in options][builtin_opts]. diff --git a/docs/markdown/Contributing.md b/docs/markdown/Contributing.md index 5332938..b16f615 100644 --- a/docs/markdown/Contributing.md +++ b/docs/markdown/Contributing.md @@ -174,7 +174,7 @@ contents of an additional file into the CI log on failure. Projects needed by unit tests are in the `test cases/unit` subdirectory. They are not run as part of `./run_project_tests.py`. -#### Configuring project tests +### Configuring project tests The (optional) `test.json` file, in the root of a test case, is used for configuring the test. All of the following root entries in the `test.json` @@ -209,17 +209,20 @@ Exanple `test.json`: { "opt1": "qwert", "opt2": "false" }, { "opt1": "bad" } ] + }, + "tools": { + "cmake": ">=3.11" } } ``` -##### env +#### env The `env` key contains a dictionary which specifies additional environment variables to be set during the configure step of the test. `@ROOT@` is replaced with the absolute path of the source directory. -##### installed +#### installed The `installed` dict contains a list of dicts, describing which files are expected to be installed. Each dict contains the following keys: @@ -277,7 +280,7 @@ the platform matches. The following values for `platform` are currently supporte | `cygwin` | Matches when the platform is cygwin | | `!cygwin` | Not `cygwin` | -##### matrix +#### matrix The `matrix` section can be used to define a test matrix to run project tests with different meson options. @@ -318,12 +321,40 @@ The above example will produce the following matrix entries: - `opt1=qwert` - `opt1=qwert opt2=true` -##### do_not_set_opts +#### do_not_set_opts Currently supported values are: - `prefix` - `libdir` +#### tools + +This section specifies a dict of tool requirements in a simple key-value format. +If a tool is specified, it has to be present in the environment, and the version +requirement must be fulfilled. Otherwise, the entire test is skipped (including +every element in the test matrix). + +#### stdout + +The `stdout` key contains a list of dicts, describing the expected stdout. + +Each dict contains the following keys: + +- `line` +- `match` (optional) + +Each item in the list is matched, in order, against the remaining actual stdout +lines, after any previous matches. If the actual stdout is exhausted before +every item in the list is matched, the expected output has not been seen, and +the test has failed. + +The `match` element of the dict determines how the `line` element is matched: + +| Type | Description | +| -------- | ----------------------- | +| `literal` | Literal match (default) | +| `re` | regex match | + ### Skipping integration tests Meson uses several continuous integration testing systems that have slightly diff --git a/docs/markdown/Cross-compilation.md b/docs/markdown/Cross-compilation.md index 4c4b7bf..1c53dcf 100644 --- a/docs/markdown/Cross-compilation.md +++ b/docs/markdown/Cross-compilation.md @@ -231,13 +231,10 @@ The main *meson* object provides two functions to determine cross compilation status. ```meson -meson.is_cross_build() # returns true when cross compiling -meson.has_exe_wrapper() # returns true if an exe wrapper has been defined +meson.is_cross_build() # returns true when cross compiling +meson.can_run_host_binaries() # returns true if the host binaries can be run, either with a wrapper or natively ``` -Note that the latter gives undefined return value when doing a native -build. - You can run system checks on both the system compiler or the cross compiler. You just have to specify which one to use. diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index 17c9991..a8f6d8a 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -76,7 +76,7 @@ and config-tool based variables. ```meson foo_dep = dependency('foo') -var = foo.get_variable(cmake : 'CMAKE_VAR', pkgconfig : 'pkg-config-var', configtool : 'get-var', default_value : 'default') +var = foo_dep.get_variable(cmake : 'CMAKE_VAR', pkgconfig : 'pkg-config-var', configtool : 'get-var', default_value : 'default') ``` It accepts the keywords 'cmake', 'pkgconfig', 'pkgconfig_define', @@ -242,6 +242,9 @@ libgcrypt_dep = dependency('libgcrypt', version: '>= 1.8') gpgme_dep = dependency('gpgme', version: '>= 1.0') ``` +*Since 0.55.0* Meson won't search $PATH any more for a config tool binary when +cross compiling if the config tool did not have an entry in the cross file. + ## AppleFrameworks Use the `modules` keyword to list frameworks required, e.g. diff --git a/docs/markdown/IDE-integration.md b/docs/markdown/IDE-integration.md index a6c6f4b..f51075e 100644 --- a/docs/markdown/IDE-integration.md +++ b/docs/markdown/IDE-integration.md @@ -29,16 +29,16 @@ watch for changes in this directory to know when something changed. The `meson-info` directory should contain the following files: -| File | Description | -| ---- | ----------- | -| `intro-benchmarks.json` | Lists all benchmarks | -| `intro-buildoptions.json` | Contains a full list of meson configuration options for the project | -| `intro-buildsystem_files.json` | Full list of all meson build files | -| `intro-dependencies.json` | Lists all dependencies used in the project | -| `intro-installed.json` | Contains mapping of files to their installed location | -| `intro-projectinfo.json` | Stores basic information about the project (name, version, etc.) | -| `intro-targets.json` | Full list of all build targets | -| `intro-tests.json` | Lists all tests with instructions how to run them | +| File | Description | +| ------------------------------ | ------------------------------------------------------------------- | +| `intro-benchmarks.json` | Lists all benchmarks | +| `intro-buildoptions.json` | Contains a full list of meson configuration options for the project | +| `intro-buildsystem_files.json` | Full list of all meson build files | +| `intro-dependencies.json` | Lists all dependencies used in the project | +| `intro-installed.json` | Contains mapping of files to their installed location | +| `intro-projectinfo.json` | Stores basic information about the project (name, version, etc.) | +| `intro-targets.json` | Full list of all build targets | +| `intro-tests.json` | Lists all tests with instructions how to run them | The content of the JSON files is further specified in the remainder of this document. @@ -99,15 +99,15 @@ for actual compilation. The following table shows all valid types for a target. -| value of `type` | Description | -| --------------- | ----------- | -| `executable` | This target will generate an executable file | -| `static library` | Target for a static library | -| `shared library` | Target for a shared library | +| value of `type` | Description | +| ---------------- | --------------------------------------------------------------------------------------------- | +| `executable` | This target will generate an executable file | +| `static library` | Target for a static library | +| `shared library` | Target for a shared library | | `shared module` | A shared library that is meant to be used with dlopen rather than linking into something else | -| `custom` | A custom target | -| `run` | A Meson run target | -| `jar` | A Java JAR target | +| `custom` | A custom target | +| `run` | A Meson run target | +| `jar` | A Java JAR target | ### Using `--targets` without a build directory @@ -275,11 +275,62 @@ command line. Use `meson introspect -h` to see all available options. This API can also work without a build directory for the `--projectinfo` command. +# AST of a `meson.build` + +Since meson *0.55.0* it is possible to dump the AST of a `meson.build` as a JSON +object. The interface for this is `meson introspect --ast /path/to/meson.build`. + +Each node of the AST has at least the following entries: + +| Key | Description | +| ------------ | ------------------------------------------------------- | +| `node` | Type of the node (see following table) | +| `lineno` | Line number of the node in the file | +| `colno` | Column number of the node in the file | +| `end_lineno` | Marks the end of the node (may be the same as `lineno`) | +| `end_colno` | Marks the end of the node (may be the same as `colno`) | + +Possible values for `node` with additional keys: + +| Node type | Additional keys | +| -------------------- | ------------------------------------------------ | +| `BooleanNode` | `value`: bool | +| `IdNode` | `value`: str | +| `NumberNode` | `value`: int | +| `StringNode` | `value`: str | +| `ContinueNode` | | +| `BreakNode` | | +| `ArgumentNode` | `positional`: node list; `kwargs`: accept_kwargs | +| `ArrayNode` | `args`: node | +| `DictNode` | `args`: node | +| `EmptyNode` | | +| `OrNode` | `left`: node; `right`: node | +| `AndNode` | `left`: node; `right`: node | +| `ComparisonNode` | `left`: node; `right`: node; `ctype`: str | +| `ArithmeticNode` | `left`: node; `right`: node; `op`: str | +| `NotNode` | `right`: node | +| `CodeBlockNode` | `lines`: node list | +| `IndexNode` | `object`: node; `index`: node | +| `MethodNode` | `object`: node; `args`: node; `name`: str | +| `FunctionNode` | `args`: node; `name`: str | +| `AssignmentNode` | `value`: node; `var_name`: str | +| `PlusAssignmentNode` | `value`: node; `var_name`: str | +| `ForeachClauseNode` | `items`: node; `block`: node; `varnames`: list | +| `IfClauseNode` | `ifs`: node list; `else`: node | +| `IfNode` | `condition`: node; `block`: node | +| `UMinusNode` | `right`: node | +| `TernaryNode` | `condition`: node; `true`: node; `false`: node | + +We do not guarantee the stability of this format since it is heavily linked to +the internal Meson AST. However, breaking changes (removal of a node type or the +removal of a key) are unlikely and will be announced in the release notes. + + # 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) +- [Meson Cmake Wrapper](https://github.com/prozum/meson-cmake-wrapper) (for cmake IDEs) (currently unmaintained !!) - [Meson-UI](https://github.com/michaelbadcrumble/meson-ui) (Meson build GUI) - [Meson Syntax Highlighter](https://plugins.jetbrains.com/plugin/13269-meson-syntax-highlighter) plugin for JetBrains IDEs. diff --git a/docs/markdown/Kconfig-module.md b/docs/markdown/Keyval-module.md index 5807f8d..643265e 100644 --- a/docs/markdown/Kconfig-module.md +++ b/docs/markdown/Keyval-module.md @@ -1,15 +1,15 @@ --- -short-description: Unstable kconfig module +short-description: Unstable keyval module authors: - name: Mark Schulte, Paolo Bonzini years: [2017, 2019] has-copyright: false ... -# Unstable kconfig module +# keyval module -This module parses Kconfig output files to allow use of kconfig -configurations in meson projects. +This module parses files consisting of a series of `key=value` lines. One use +of this module is to load kconfig configurations in meson projects. **Note**:Â this does not provide kconfig frontend tooling to generate a configuration. You still need something such as kconfig frontends (see @@ -23,20 +23,23 @@ chosen the configuration options), output a ".config" file. The module may be imported as follows: ``` meson -kconfig = import('unstable-kconfig') +keyval = import('unstable-keyval') ``` The following functions will then be available as methods on the object -with the name `kconfig`. You can, of course, replace the name -`kconfig` with anything else. +with the name `keyval`. You can, of course, replace the name +`keyval` with anything else. -### kconfig.load() +### keyval.load() -This function loads a kconfig output file and returns a dictionary object. +This function loads a file consisting of a series of `key=value` lines +and returns a dictionary object. -`kconfig.load()` makes no attempt at parsing the values in the -file. Therefore, true boolean values will be represented as the string "y" -and integer values will have to be converted with `.to_int()`. +`keyval.load()` makes no attempt at parsing the values in the file. +In particular boolean and integer values will be represented as strings, +and strings will keep any quoting that is present in the input file. It +can be useful to create a [`configuration_data()`](#configuration_data) +object from the dictionary and use methods such as `get_unquoted()`. Kconfig frontends usually have ".config" as the default name for the configuration file. However, placing the configuration file in the source diff --git a/docs/markdown/Precompiled-headers.md b/docs/markdown/Precompiled-headers.md index d9ac7a4..05b50bc 100644 --- a/docs/markdown/Precompiled-headers.md +++ b/docs/markdown/Precompiled-headers.md @@ -51,7 +51,7 @@ Using precompiled headers with GCC and derivatives -- Once you have a file to precompile, you can enable the use of pch for -a give target with a *pch* keyword argument. As an example, let's assume +a given target with a *pch* keyword argument. As an example, let's assume you want to build a small C binary with precompiled headers. Let's say the source files of the binary use the system headers `stdio.h` and `string.h`. Then you create a header file `pch/myexe_pch.h` with this diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 5156b5b..9b5d657 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -600,8 +600,12 @@ be passed to [shared and static libraries](#library). depends on such as a symbol visibility map. The purpose is to automatically trigger a re-link (but not a re-compile) of the target when this file changes. -- `link_language` since 0.51.0 makes the linker for this target - be for the specified language. This is helpful for multi-language targets. +- `link_language` since 0.51.0 (broken until 0.55.0) makes the linker for this + target be for the specified language. It is generally unnecessary to set + this, as meson will detect the right linker to use in most cases. There are + only two cases where this is needed. One, your main function in an + executable is not in the language meson picked, or second you want to force + a library to use only one ABI. - `link_whole` links all contents of the given static libraries whether they are used by not, equivalent to the `-Wl,--whole-archive` argument flag of GCC, available since 0.40.0. @@ -1609,6 +1613,13 @@ object](#build-target-object) returned by object](#external-program-object) returned by [`find_program()`](#find_program). +*Since 0.55.0* When cross compiling, if an exe_wrapper is needed and defined +the environment variable `MESON_EXE_WRAPPER` will be set to the string value +of that wrapper (implementation detail: using `mesonlib.join_args`). Test +scripts may use this to run cross built binaries. If your test needs +`MESON_EXE_WRAPPER` in cross build situations it is your responsibility to +return code 77 to tell the harness to report "skip" + By default, environment variable [`MALLOC_PERTURB_`](http://man7.org/linux/man-pages/man3/mallopt.3.html) is automatically set by `meson test` to a random value between 1..255. @@ -1662,11 +1673,14 @@ test(..., env: nomalloc, ...) before test is executed even if they have `build_by_default : false`. Since 0.46.0 -- `protocol` specifies how the test results are parsed and can be one - of `exitcode` (the executable's exit code is used by the test harness - to record the outcome of the test) or `tap` ([Test Anything - Protocol](https://www.testanything.org/)). For more on the Meson test - harness protocol read [Unit Tests](Unit-tests.md). Since 0.50.0 +- `protocol` *(Since 0.50.0)* specifies how the test results are parsed and can + be one of `exitcode`, `tap`, or `gtest`. For more information about test + harness protocol read [Unit Tests](Unit-tests.md). The following values are + accepted: + - `exitcode`: the executable's exit code is used by the test harness + to record the outcome of the test) + - `tap` ([Test Anything Protocol](https://www.testanything.org/)) + - `gtest`. *(Since 0.55.0)* for Google Tests. - `priority` specifies the priority of a test. Tests with a higher priority are *started* before tests with a lower priority. @@ -1735,6 +1749,8 @@ the following methods. 0.49.0, the function only accepted a single argument. Since 0.54.0 the `MESON_SOURCE_ROOT` and `MESON_BUILD_ROOT` environment variables are set when dist scripts are run. + *(Since 0.55.0)* The output of `configure_file`, `files`, and `find_program` + as well as strings. - `add_install_script(script_name, arg1, arg2, ...)` causes the script given as an argument to be run during the install step, this script @@ -1742,6 +1758,9 @@ the following methods. `MESON_BUILD_ROOT`, `MESON_INSTALL_PREFIX`, `MESON_INSTALL_DESTDIR_PREFIX`, and `MESONINTROSPECT` set. All positional arguments are passed as parameters. + *(Since 0.55.0)* The output of `configure_file`, `files`, `find_program`, + `custom_target`, indexes of `custom_target`, `executable`, `library`, and + other built targets as well as strings. *(added 0.54)* If `meson install` is called with the `--quiet` option, the environment variable `MESON_INSTALL_QUIET` will be set. @@ -1772,6 +1791,8 @@ the following methods. executable given as an argument after all project files have been generated. This script will have the environment variables `MESON_SOURCE_ROOT` and `MESON_BUILD_ROOT` set. + *(Since 0.55.0)* The output of `configure_file`, `files`, and `find_program` + as well as strings. - `backend()` *(added 0.37.0)* returns a string representing the current backend: `ninja`, `vs2010`, `vs2015`, `vs2017`, `vs2019`, @@ -1822,10 +1843,14 @@ the following methods. If `native: false` or not specified, variable is retrieved from the cross-file if cross-compiling, and from the native-file when not cross-compiling. -- `has_exe_wrapper()` returns true when doing a cross build if there - is a wrapper command that can be used to execute cross built - binaries (for example when cross compiling from Linux to Windows, - one can use `wine` as the wrapper). +- `can_run_host_binaries()` returns true if the build machine can run + binaries compiled for the host. This returns true unless you are + cross compiling, need a helper to run host binaries, and don't have one. + For example when cross compiling from Linux to Windows, one can use `wine` + as the helper. *New in 0.55.0* + +- `has_exe_wrapper()` alias of `can_run_host_binaries` + *Deprecated since 0.55.0* - `install_dependency_manifest(output_name)` installs a manifest file containing a list of all subprojects, their versions and license @@ -1846,7 +1871,9 @@ the following methods. specifies that whenever `find_program` is used to find a program named `progname`, Meson should not look it up on the system but instead return `program`, which may either be the result of - `find_program`, `configure_file` or `executable`. + `find_program`, `configure_file` or `executable`. *Since 0.55.0* if a version + check is passed to `find_program` for a program that has been overridden with + an executable, the current project version is used. If `program` is an `executable`, it cannot be used during configure. @@ -2460,6 +2487,12 @@ 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. For example: `run_command(obj, arg1, arg2)` + *Since 0.55.0* this method has been deprecated in favor of `full_path()` for + consistency with other returned objects. + +- `full_path()` *Since 0.55.0* 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. For example: `run_command(obj, arg1, arg2)`. ### `environment` object diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md index dfae339..c42d608 100644 --- a/docs/markdown/Reference-tables.md +++ b/docs/markdown/Reference-tables.md @@ -81,6 +81,7 @@ set in the cross file. | alpha | DEC Alpha processor | | arc | 32 bit ARC processor | | arm | 32 bit ARM processor | +| avr | Atmel AVR processor | | e2k | MCST Elbrus processor | | c2000 | 32 bit C2000 processor | | ia64 | Itanium processor | diff --git a/docs/markdown/Release-notes-for-0.54.0.md b/docs/markdown/Release-notes-for-0.54.0.md index 2c8880c..3202b57 100644 --- a/docs/markdown/Release-notes-for-0.54.0.md +++ b/docs/markdown/Release-notes-for-0.54.0.md @@ -14,7 +14,7 @@ If it set to 0 then the PTHREAD_POOL_SIZE option will not be passed. ## Introduce dataonly for the pkgconfig module This allows users to disable writing out the inbuilt variables to -the pkg-config file as they might actualy not be required. +the pkg-config file as they might actually not be required. One reason to have this is for architecture-independent pkg-config files in projects which also have architecture-dependent outputs. diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md index cf0516c..666d50e 100644 --- a/docs/markdown/Syntax.md +++ b/docs/markdown/Syntax.md @@ -588,3 +588,73 @@ FAQ](FAQ.md#why-is-meson-not-just-a-python-module-so-i-could-code-my-build-setup because of this limitation you find yourself copying and pasting code a lot you may be able to use a [`foreach` loop instead](#foreach-statements). + +Stability Promises +-- + +Meson is very actively developed and continuously improved. There is a +possibility that future enhancements to the Meson build system will require +changes to the syntax. Such changes might be the addition of new reserved +keywords, changing the meaning of existing keywords or additions around the +basic building blocks like statements and fundamental types. It is planned +to stabilize the syntax with the 1.0 release. + +Grammar +-- + +This is the full Meson grammar, as it is used to parse Meson build definition files: + +``` +additive_expression: multiplicative_expression | (additive_expression additive_operator multiplicative_expression) +additive_operator: "+" | "-" +argument_list: positional_arguments ["," keyword_arguments] | keyword_arguments +array_literal: "[" [expression_list] "]" +assignment_expression: conditional_expression | (logical_or_expression assignment_operator assignment_expression) +assignment_operator: "=" | "*=" | "/=" | "%=" | "+=" | "-=" +boolean_literal: "true" | "false" +build_definition: (NEWLINE | statement)* +condition: expression +conditional_expression: logical_or_expression | (logical_or_expression "?" expression ":" assignment_expression +decimal_literal: DECIMAL_NUMBER +DECIMAL_NUMBER: /[1-9][0-9]*/ +dictionary_literal: "{" [key_value_list] "}" +equality_expression: relational_expression | (equality_expression equality_operator relational_expression) +equality_operator: "==" | "!=" +expression: assignment_expression +expression_list: expression ("," expression)* +expression_statememt: expression +function_expression: id_expression "(" [argument_list] ")" +hex_literal: "0x" HEX_NUMBER +HEX_NUMBER: /[a-fA-F0-9]+/ +id_expression: IDENTIFIER +IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/ +identifier_list: id_expression ("," id_expression)* +integer_literal: decimal_literal | octal_literal | hex_literal +iteration_statement: "foreach" identifier_list ":" id_expression NEWLINE (statement | jump_statement)* "endforeach" +jump_statement: ("break" | "continue") NEWLINE +key_value_item: expression ":" expression +key_value_list: key_value_item ("," key_value_item)* +keyword_item: id_expression ":" expression +keyword_arguments: keyword_item ("," keyword_item)* +literal: integer_literal | string_literal | boolean_literal | array_literal | dictionary_literal +logical_and_expression: equality_expression | (logical_and_expression "and" equality_expression) +logical_or_expression: logical_and_expression | (logical_or_expression "or" logical_and_expression) +method_expression: postfix_expression "." function_expression +multiplicative_expression: unary_expression | (multiplicative_expression multiplicative_operator unary_expression) +multiplicative_operator: "*" | "/" | "%" +octal_literal: "0o" OCTAL_NUMBER +OCTAL_NUMBER: /[0-7]+/ +positional_arguments: expression ("," expression)* +postfix_expression: primary_expression | subscript_expression | function_expression | method_expression +primary_expression: literal | ("(" expression ")") | id_expression +relational_expression: additive_expression | (relational_expression relational_operator additive_expression) +relational_operator: ">" | "<" | ">=" | "<=" | "in" | ("not" "in") +selection_statement: "if" condition NEWLINE (statement)* ("elif" condition NEWLINE (statement)*)* ["else" (statement)*] "endif" +statement: (expression_statement | selection_statement | iteration_statement) NEWLINE +string_literal: ("'" STRING_SIMPLE_VALUE "'") | ("'''" STRING_MULTILINE_VALUE "'''") +STRING_MULTILINE_VALUE: \.*?(''')\ +STRING_SIMPLE_VALUE: \.*?(?<!\\)(\\\\)*?'\ +subscript_expression: postfix_expression "[" expression "]" +unary_expression: postfix_expression | (unary_operator unary_expression) +unary_operator: "not" | "+" | "-" +``` diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index 0785549..bd91dbb 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -4,20 +4,24 @@ short-description: Meson's own unit-test system # Unit tests -Meson comes with a fully functional unit test system. To use it simply build an executable and then use it in a test. +Meson comes with a fully functional unit test system. To use it simply build +an executable and then use it in a test. ```meson e = executable('prog', 'testprog.c') test('name of test', e) ``` -You can add as many tests as you want. They are run with the command `ninja test`. +You can add as many tests as you want. They are run with the command `ninja +test`. -Meson captures the output of all tests and writes it in the log file `meson-logs/testlog.txt`. +Meson captures the output of all tests and writes it in the log file +`meson-logs/testlog.txt`. ## Test parameters -Some tests require the use of command line arguments or environment variables. These are simple to define. +Some tests require the use of command line arguments or environment +variables. These are simple to define. ```meson test('command line test', exe, args : ['first', 'second']) @@ -29,38 +33,46 @@ Note how you need to specify multiple values as an array. ### MALLOC_PERTURB_ By default, environment variable -[`MALLOC_PERTURB_`](http://man7.org/linux/man-pages/man3/mallopt.3.html) -is set to a random value between 1..255. This can help find memory -leaks on configurations using glibc, including with non-GCC compilers. -This feature can be disabled as discussed in [test()](Reference-manual.md#test). +[`MALLOC_PERTURB_`](http://man7.org/linux/man-pages/man3/mallopt.3.html) is +set to a random value between 1..255. This can help find memory leaks on +configurations using glibc, including with non-GCC compilers. This feature +can be disabled as discussed in [test()](Reference-manual.md#test). ## Coverage If you enable coverage measurements by giving Meson the command line flag -`-Db_coverage=true`, you can generate coverage reports after running the tests -(running the tests is required to gather the list of functions that get -called). Meson will autodetect what coverage generator tools you have installed -and will generate the corresponding targets. These targets are `coverage-xml` -and `coverage-text` which are both provided by [Gcovr](http://gcovr.com) -(version 3.3 or higher) and `coverage-html`, which requires -[Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and -[GenHTML](https://linux.die.net/man/1/genhtml) or -[Gcovr](http://gcovr.com). As a convenience, a high-level `coverage` target is -also generated which will produce all 3 coverage report types, if possible. - -The output of these commands is written to the log directory `meson-logs` in your build directory. +`-Db_coverage=true`, you can generate coverage reports after running the +tests (running the tests is required to gather the list of functions that get +called). Meson will autodetect what coverage generator tools you have +installed and will generate the corresponding targets. These targets are +`coverage-xml` and `coverage-text` which are both provided by +[Gcovr](http://gcovr.com) (version 3.3 or higher) and `coverage-html`, which +requires [Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and +[GenHTML](https://linux.die.net/man/1/genhtml) or [Gcovr](http://gcovr.com). +As a convenience, a high-level `coverage` target is also generated which will +produce all 3 coverage report types, if possible. + +The output of these commands is written to the log directory `meson-logs` in +your build directory. ## Parallelism -To reduce test times, Meson will by default run multiple unit tests in parallel. It is common to have some tests which can not be run in parallel because they require unique hold on some resource such as a file or a D-Bus name. You have to specify these tests with a keyword argument. +To reduce test times, Meson will by default run multiple unit tests in +parallel. It is common to have some tests which can not be run in parallel +because they require unique hold on some resource such as a file or a D-Bus +name. You have to specify these tests with a keyword argument. ```meson test('unique test', t, is_parallel : false) ``` -Meson will then make sure that no other unit test is running at the same time. Non-parallel tests take longer to run so it is recommended that you write your unit tests to be parallel executable whenever possible. +Meson will then make sure that no other unit test is running at the same +time. Non-parallel tests take longer to run so it is recommended that you +write your unit tests to be parallel executable whenever possible. -By default Meson uses as many concurrent processes as there are cores on the test machine. You can override this with the environment variable `MESON_TESTTHREADS` like this. +By default Meson uses as many concurrent processes as there are cores on the +test machine. You can override this with the environment variable +`MESON_TESTTHREADS` like this. ```console $ MESON_TESTTHREADS=5 ninja test @@ -70,7 +82,10 @@ $ MESON_TESTTHREADS=5 ninja test *(added in version 0.52.0)* -Tests can be assigned a priority that determines when a test is *started*. Tests with higher priority are started first, tests with lower priority started later. The default priority is 0, meson makes no guarantee on the ordering of tests with identical priority. +Tests can be assigned a priority that determines when a test is *started*. +Tests with higher priority are started first, tests with lower priority +started later. The default priority is 0, meson makes no guarantee on the +ordering of tests with identical priority. ```meson test('started second', t, priority : 0) @@ -78,23 +93,37 @@ test('started third', t, priority : -50) test('started first', t, priority : 1000) ``` -Note that the test priority only affects the starting order of tests and subsequent tests are affected by how long it takes previous tests to complete. It is thus possible that a higher-priority test is still running when lower-priority tests with a shorter runtime have completed. +Note that the test priority only affects the starting order of tests and +subsequent tests are affected by how long it takes previous tests to +complete. It is thus possible that a higher-priority test is still running +when lower-priority tests with a shorter runtime have completed. ## Skipped tests and hard errors Sometimes a test can only determine at runtime that it can not be run. -For the default `exitcode` testing protocol, the GNU standard approach in this case is to exit the program with error code 77. Meson will detect this and report these tests as skipped rather than failed. This behavior was added in version 0.37.0. +For the default `exitcode` testing protocol, the GNU standard approach in +this case is to exit the program with error code 77. Meson will detect this +and report these tests as skipped rather than failed. This behavior was added +in version 0.37.0. -For TAP-based tests, skipped tests should print a single line starting with `1..0 # SKIP`. +For TAP-based tests, skipped tests should print a single line starting with +`1..0 # SKIP`. -In addition, sometimes a test fails set up so that it should fail even if it is marked as an expected failure. The GNU standard approach in this case is to exit the program with error code 99. Again, Meson will detect this and report these tests as `ERROR`, ignoring the setting of `should_fail`. This behavior was added in version 0.50.0. +In addition, sometimes a test fails set up so that it should fail even if it +is marked as an expected failure. The GNU standard approach in this case is +to exit the program with error code 99. Again, Meson will detect this and +report these tests as `ERROR`, ignoring the setting of `should_fail`. This +behavior was added in version 0.50.0. ## Testing tool -The goal of the meson test tool is to provide a simple way to run tests in a variety of different ways. The tool is designed to be run in the build directory. +The goal of the meson test tool is to provide a simple way to run tests in a +variety of different ways. The tool is designed to be run in the build +directory. -The simplest thing to do is just to run all tests, which is equivalent to running `ninja test`. +The simplest thing to do is just to run all tests, which is equivalent to +running `ninja test`. ```console $ meson test @@ -125,7 +154,8 @@ Tests belonging to a suite `suite` can be run as follows $ meson test --suite (sub)project_name:suite ``` -Since version *0.46*, `(sub)project_name` can be omitted if it is the top-level project. +Since version *0.46*, `(sub)project_name` can be omitted if it is the +top-level project. Multiple suites are specified like: @@ -145,7 +175,8 @@ Sometimes you need to run the tests multiple times, which is done like this: $ meson test --repeat=10 ``` -Invoking tests via a helper executable such as Valgrind can be done with the `--wrap` argument +Invoking tests via a helper executable such as Valgrind can be done with the +`--wrap` argument ```console $ meson test --wrap=valgrind testname @@ -163,17 +194,25 @@ Meson also supports running the tests under GDB. Just doing this: $ meson test --gdb testname ``` -Meson will launch `gdb` all set up to run the test. Just type `run` in the GDB command prompt to start the program. +Meson will launch `gdb` all set up to run the test. Just type `run` in the +GDB command prompt to start the program. -The second use case is a test that segfaults only rarely. In this case you can invoke the following command: +The second use case is a test that segfaults only rarely. In this case you +can invoke the following command: ```console $ meson test --gdb --repeat=10000 testname ``` -This runs the test up to 10 000 times under GDB automatically. If the program crashes, GDB will halt and the user can debug the application. Note that testing timeouts are disabled in this case so `meson test` will not kill `gdb` while the developer is still debugging it. The downside is that if the test binary freezes, the test runner will wait forever. +This runs the test up to 10 000 times under GDB automatically. If the program +crashes, GDB will halt and the user can debug the application. Note that +testing timeouts are disabled in this case so `meson test` will not kill +`gdb` while the developer is still debugging it. The downside is that if the +test binary freezes, the test runner will wait forever. -Sometimes, the GDB binary is not in the PATH variable or the user wants to use a GDB replacement. Therefore, the invoked GDB program can be specified *(added 0.52.0)*: +Sometimes, the GDB binary is not in the PATH variable or the user wants to +use a GDB replacement. Therefore, the invoked GDB program can be specified +*(added 0.52.0)*: ```console $ meson test --gdb --gdb-path /path/to/gdb testname @@ -183,12 +222,41 @@ $ meson test --gdb --gdb-path /path/to/gdb testname $ meson test --print-errorlogs ``` -Meson will report the output produced by the failing tests along with other useful information as the environmental variables. This is useful, for example, when you run the tests on Travis-CI, Jenkins and the like. +Meson will report the output produced by the failing tests along with other +useful information as the environmental variables. This is useful, for +example, when you run the tests on Travis-CI, Jenkins and the like. -For further information see the command line help of Meson by running `meson test -h`. +For further information see the command line help of Meson by running `meson +test -h`. ## 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. +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. + +## Test outputs + +Meson will write several different files with detailed results of running +tests. These will be written into $builddir/meson-logs/ + +### testlog.json + +This is not a proper json file, but a file containing one valid json object +per line. This is file is designed so each line is streamed out as each test +is run, so it can be read as a stream while the test harness is running + +### testlog.junit.xml + +This is a valid JUnit XML description of all tests run. It is not streamed +out, and is written only once all tests complete running. + +When tests use the `tap` protocol each test will be recorded as a testsuite +container, with each case named by the number of the result. + +When tests use the `gtest` protocol meson will inject arguments to the test +to generate it's own JUnit XML, which meson will include as part of this XML +file. + +*New in 0.55.0* diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md index bfc8a7a..41d8dfa 100644 --- a/docs/markdown/Users.md +++ b/docs/markdown/Users.md @@ -124,6 +124,7 @@ format files - [Terminology](https://github.com/billiob/terminology), a terminal emulator based on the Enlightenment Foundation Libraries - [Tilix](https://github.com/gnunn1/tilix), a tiling terminal emulator for Linux using GTK+ 3 - [Tizonia](https://github.com/tizonia/tizonia-openmax-il), a command-line cloud music player for Linux with support for Spotify, Google Play Music, YouTube, SoundCloud, TuneIn, Plex servers and Chromecast devices + - [Vala Language Server](https://github.com/benwaffle/vala-language-server), code intelligence engine for the Vala and Genie programming languages - [Valum](https://github.com/valum-framework/valum), a micro web framework written in Vala - [Venom](https://github.com/naxuroqa/Venom), a modern Tox client for the GNU/Linux desktop - [VMAF](https://github.com/Netflix/vmaf) (by Netflix), a perceptual video quality assessment based on multi-method fusion diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md index 8231d3d..84546b7 100644 --- a/docs/markdown/howtox.md +++ b/docs/markdown/howtox.md @@ -260,3 +260,28 @@ The `cmake_module_path` property is only needed for custom CMake scripts. System wide CMake scripts are found automatically. More information can be found [here](Dependencies.md#cmake) + +## Get a default not-found dependency? + +```meson +null_dep = dependency('', required : false) +``` + +This can be used in cases where you want a default value, but might override it +later. + +```meson +# Not needed on Windows! +my_dep = dependency('', required : false) +if host_machine.system() in ['freebsd', 'netbsd', 'openbsd', 'dragonfly'] + my_dep = dependency('some dep', required : false) +elif host_machine.system() == 'linux' + my_dep = dependency('some other dep', required : false) +endif + +executable( + 'myexe', + my_sources, + deps : [my_dep] +) +``` diff --git a/docs/markdown/snippets/add_foo_script_type_additions.md b/docs/markdown/snippets/add_foo_script_type_additions.md new file mode 100644 index 0000000..88a88b2 --- /dev/null +++ b/docs/markdown/snippets/add_foo_script_type_additions.md @@ -0,0 +1,24 @@ +## meson.add_*_script methods accept new types + +All three (`add_install_script`, `add_dist_script`, and +`add_postconf_script`) now accept ExternalPrograms (as returned by +`find_program`), Files, and the output of `configure_file`. The dist and +postconf methods cannot accept other types because of when they are run. +While dist could, in theory, take other dependencies, it would require more +extensive changes, particularly to the backend. + +```meson +meson.add_install_script(find_program('foo'), files('bar')) +meson.add_dist_script(find_program('foo'), files('bar')) +meson.add_postconf_script(find_program('foo'), files('bar')) +``` + +The install script variant is also able to accept custom_targets, +custom_target indexes, and build targets (executables, libraries), and can +use built executables a the script to run + +```meson +installer = executable('installer', ...) +meson.add_install_script(installer, ...) +meson.add_install_script('foo.py', installer) +``` diff --git a/docs/markdown/snippets/can_run_host_binaries.md b/docs/markdown/snippets/can_run_host_binaries.md new file mode 100644 index 0000000..0108184 --- /dev/null +++ b/docs/markdown/snippets/can_run_host_binaries.md @@ -0,0 +1,5 @@ +## Rename has_exe_wrapper -> can_run_host_binaries + +The old name was confusing as it didn't really match the behavior of the +function. The old name remains as an alias (the behavior hasn't changed), but +is now deprecated. diff --git a/docs/markdown/snippets/config_tool_no_cross_path.md b/docs/markdown/snippets/config_tool_no_cross_path.md new file mode 100644 index 0000000..cec22e4 --- /dev/null +++ b/docs/markdown/snippets/config_tool_no_cross_path.md @@ -0,0 +1,7 @@ +## Config tool based dependencies no longer search PATH for cross compiling + +Before 0.55.0 config tool based dependencies (llvm-config, cups-config, etc), +would search system $PATH if they weren't defined in the cross file. This has +been a source of bugs and has been deprecated. It is now removed, config tool +binaries must be specified in the cross file now or the dependency will not +be found. diff --git a/docs/markdown/snippets/d-lang_n_debug.md b/docs/markdown/snippets/d-lang_n_debug.md new file mode 100644 index 0000000..59f09e4 --- /dev/null +++ b/docs/markdown/snippets/d-lang_n_debug.md @@ -0,0 +1,4 @@ +## b_ndebug support for D language compilers + +D Language compilers will now set -release/--release/-frelease (depending on +the compiler) when the b_ndebug flag is set. diff --git a/docs/markdown/snippets/exe_wrapper_for_cross_built_tests.md b/docs/markdown/snippets/exe_wrapper_for_cross_built_tests.md new file mode 100644 index 0000000..ebdd8a7 --- /dev/null +++ b/docs/markdown/snippets/exe_wrapper_for_cross_built_tests.md @@ -0,0 +1,9 @@ +## Test scripts are given the exe wrapper if needed + +Meson will now set the `MESON_EXE_WRAPPER` as the properly wrapped and joined +representation. For Unix-like OSes this means python's shelx.join, on Windows +an implementation that attempts to properly quote windows argument is used. +This allow wrapper scripts to run test binaries, instead of just skipping. + +for example, if the wrapper is `['emulator', '--script']`, it will be passed +as `MESON_EXE_WRAPPER="emulator --script"`. diff --git a/docs/markdown/snippets/find_program.md b/docs/markdown/snippets/find_program.md new file mode 100644 index 0000000..d0bb64d --- /dev/null +++ b/docs/markdown/snippets/find_program.md @@ -0,0 +1,20 @@ +## find_program: Fixes when the program has been overridden by executable + +When a program has been overridden by an executable, the returned object of +find_program() had some issues: + +```meson +# In a subproject: +exe = executable('foo', ...) +meson.override_find_program('foo', exe) + +# In main project: +# The version check was crashing meson. +prog = find_program('foo', version : '>=1.0') + +# This was crashing meson. +message(prog.path()) + +# New method to be consistent with built objects. +message(prog.full_path()) +``` diff --git a/docs/markdown/snippets/gtest_protocol.md b/docs/markdown/snippets/gtest_protocol.md new file mode 100644 index 0000000..14f3af9 --- /dev/null +++ b/docs/markdown/snippets/gtest_protocol.md @@ -0,0 +1,6 @@ +## Test protocol for gtest + +Due to the popularity of Gtest (google test) among C and C++ developers meson +now supports a special protocol for gtest. With this protocol meson injects +arguments to gtests to output JUnit, reads that JUnit, and adds the output to +the JUnit it generates. diff --git a/docs/markdown/snippets/introspect.md b/docs/markdown/snippets/introspect.md new file mode 100644 index 0000000..8eab486 --- /dev/null +++ b/docs/markdown/snippets/introspect.md @@ -0,0 +1,4 @@ +## Introspection API changes + +dumping the AST (--ast): **new in 0.55.0** +- prints the AST of a meson.build as JSON diff --git a/docs/markdown/snippets/junit_result_generation.md b/docs/markdown/snippets/junit_result_generation.md new file mode 100644 index 0000000..fbe910b --- /dev/null +++ b/docs/markdown/snippets/junit_result_generation.md @@ -0,0 +1,4 @@ +## Meson test now produces JUnit xml from results + +Meson will now generate a JUnit compatible XML file from test results. it +will be in the meson-logs directory and is called testlog.junit.xml. diff --git a/docs/markdown/snippets/keyval_kobject.md b/docs/markdown/snippets/keyval_kobject.md new file mode 100644 index 0000000..4add23c --- /dev/null +++ b/docs/markdown/snippets/keyval_kobject.md @@ -0,0 +1,6 @@ +## `unstable-kconfig` module renamed to `unstable-keyval` + +The `unstable-kconfig` module is now renamed to `unstable-keyval`. +We expect this module to become stable once it has some usage experience, +specifically in the next or the following release + diff --git a/docs/markdown/snippets/link_language_all_targets.md b/docs/markdown/snippets/link_language_all_targets.md new file mode 100644 index 0000000..9019d50 --- /dev/null +++ b/docs/markdown/snippets/link_language_all_targets.md @@ -0,0 +1,8 @@ +## link_language argument added to all targets + +Previously the `link_language` argument was only supposed to be allowed in +executables, because the linker used needs to be the linker for the language +that implements the main function. Unfortunately it didn't work in that case, +and, even worse, if it had been implemented properly it would have worked for +*all* targets. In 0.55.0 this restriction has been removed, and the bug fixed. +It now is valid for `executable` and all derivative of `library`. diff --git a/docs/markdown/snippets/options_string_concat.md b/docs/markdown/snippets/options_string_concat.md new file mode 100644 index 0000000..0fbf0f4 --- /dev/null +++ b/docs/markdown/snippets/options_string_concat.md @@ -0,0 +1,14 @@ +## String concatenation in meson_options.txt + +It is now possible to use string concatenation (with the `+` opperator) in the +meson_options.txt file. This allows splitting long option descriptions. + +```meson +option( + 'testoption', + type : 'string', + value : 'optval', + description : 'An option with a very long description' + + 'that does something in a specific context' +) +``` diff --git a/docs/markdown/snippets/rpath_behavior.md b/docs/markdown/snippets/rpath_behavior.md new file mode 100644 index 0000000..c46f0c2 --- /dev/null +++ b/docs/markdown/snippets/rpath_behavior.md @@ -0,0 +1,7 @@ +## rpath removal now more careful + +On Linux-like systems, meson adds rpath entries to allow running apps +in the build tree, and then removes those build-time-only +rpath entries when installing. Rpath entries may also come +in via LDFLAGS and via .pc files. Meson used to remove those +latter rpath entries by accident, but is now more careful. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index 3ac138e..4029a60 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -48,7 +48,7 @@ index.md SourceSet-module.md Windows-module.md Cuda-module.md - Kconfig-module.md + Keyval-module.md Java.md Vala.md D.md diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html index 6980f81..832bd2c 100644 --- a/docs/theme/extra/templates/navbar_links.html +++ b/docs/theme/extra/templates/navbar_links.html @@ -14,7 +14,7 @@ ("Hotdoc-module.html","Hotdoc"), \ ("i18n-module.html","i18n"), \ ("Icestorm-module.html","Icestorm"), \ - ("Kconfig-module.html","kconfig"), \ + ("Keyval-module.html","Keyval"), \ ("Pkgconfig-module.html","Pkgconfig"), \ ("Python-module.html","Python"), \ ("Python-3-module.html","Python 3"), \ diff --git a/mesonbuild/ast/__init__.py b/mesonbuild/ast/__init__.py index 48de523..4fb56cb 100644 --- a/mesonbuild/ast/__init__.py +++ b/mesonbuild/ast/__init__.py @@ -20,6 +20,7 @@ __all__ = [ 'AstInterpreter', 'AstIDGenerator', 'AstIndentationGenerator', + 'AstJSONPrinter', 'AstVisitor', 'AstPrinter', 'IntrospectionInterpreter', @@ -30,4 +31,4 @@ from .interpreter import AstInterpreter from .introspection import IntrospectionInterpreter, build_target_functions from .visitor import AstVisitor from .postprocess import AstConditionLevel, AstIDGenerator, AstIndentationGenerator -from .printer import AstPrinter +from .printer import AstPrinter, AstJSONPrinter diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index cc5c94c..6a826ef 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -297,6 +297,11 @@ class AstInterpreter(interpreterbase.InterpreterBase): elif isinstance(node, ElementaryNode): result = node.value + elif isinstance(node, NotNode): + result = self.resolve_node(node.value, include_unknown_args, id_loop_detect) + if isinstance(result, bool): + result = not result + elif isinstance(node, ArrayNode): result = [x for x in node.args.arguments] diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py index 39e2cca..a57ba20 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -18,6 +18,7 @@ from .. import mparser from . import AstVisitor import re +import typing as T arithmic_map = { 'add': '+', @@ -155,7 +156,7 @@ class AstPrinter(AstVisitor): self.append_padded(prefix + 'if', node) prefix = 'el' i.accept(self) - if node.elseblock: + if not isinstance(node.elseblock, mparser.EmptyNode): self.append('else', node) node.elseblock.accept(self) self.append('endif', node) @@ -199,3 +200,160 @@ class AstPrinter(AstVisitor): self.result = re.sub(r', \n$', '\n', self.result) else: self.result = re.sub(r', $', '', self.result) + +class AstJSONPrinter(AstVisitor): + def __init__(self) -> None: + self.result = {} # type: T.Dict[str, T.Any] + self.current = self.result + + def _accept(self, key: str, node: mparser.BaseNode) -> None: + old = self.current + data = {} # type: T.Dict[str, T.Any] + self.current = data + node.accept(self) + self.current = old + self.current[key] = data + + def _accept_list(self, key: str, nodes: T.Sequence[mparser.BaseNode]) -> None: + old = self.current + datalist = [] # type: T.List[T.Dict[str, T.Any]] + for i in nodes: + self.current = {} + i.accept(self) + datalist += [self.current] + self.current = old + self.current[key] = datalist + + def _raw_accept(self, node: mparser.BaseNode, data: T.Dict[str, T.Any]) -> None: + old = self.current + self.current = data + node.accept(self) + self.current = old + + def setbase(self, node: mparser.BaseNode) -> None: + self.current['node'] = type(node).__name__ + self.current['lineno'] = node.lineno + self.current['colno'] = node.colno + self.current['end_lineno'] = node.end_lineno + self.current['end_colno'] = node.end_colno + + def visit_default_func(self, node: mparser.BaseNode) -> None: + self.setbase(node) + + def gen_ElementaryNode(self, node: mparser.ElementaryNode) -> None: + self.current['value'] = node.value + self.setbase(node) + + def visit_BooleanNode(self, node: mparser.BooleanNode) -> None: + self.gen_ElementaryNode(node) + + def visit_IdNode(self, node: mparser.IdNode) -> None: + self.gen_ElementaryNode(node) + + def visit_NumberNode(self, node: mparser.NumberNode) -> None: + self.gen_ElementaryNode(node) + + def visit_StringNode(self, node: mparser.StringNode) -> None: + self.gen_ElementaryNode(node) + + def visit_ArrayNode(self, node: mparser.ArrayNode) -> None: + self._accept('args', node.args) + self.setbase(node) + + def visit_DictNode(self, node: mparser.DictNode) -> None: + self._accept('args', node.args) + self.setbase(node) + + def visit_OrNode(self, node: mparser.OrNode) -> None: + self._accept('left', node.left) + self._accept('right', node.right) + self.setbase(node) + + def visit_AndNode(self, node: mparser.AndNode) -> None: + self._accept('left', node.left) + self._accept('right', node.right) + self.setbase(node) + + def visit_ComparisonNode(self, node: mparser.ComparisonNode) -> None: + self._accept('left', node.left) + self._accept('right', node.right) + self.current['ctype'] = node.ctype + self.setbase(node) + + def visit_ArithmeticNode(self, node: mparser.ArithmeticNode) -> None: + self._accept('left', node.left) + self._accept('right', node.right) + self.current['op'] = arithmic_map[node.operation] + self.setbase(node) + + def visit_NotNode(self, node: mparser.NotNode) -> None: + self._accept('right', node.value) + self.setbase(node) + + def visit_CodeBlockNode(self, node: mparser.CodeBlockNode) -> None: + self._accept_list('lines', node.lines) + self.setbase(node) + + def visit_IndexNode(self, node: mparser.IndexNode) -> None: + self._accept('object', node.iobject) + self._accept('index', node.index) + self.setbase(node) + + def visit_MethodNode(self, node: mparser.MethodNode) -> None: + self._accept('object', node.source_object) + self._accept('args', node.args) + self.current['name'] = node.name + self.setbase(node) + + def visit_FunctionNode(self, node: mparser.FunctionNode) -> None: + self._accept('args', node.args) + self.current['name'] = node.func_name + self.setbase(node) + + def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None: + self._accept('value', node.value) + self.current['var_name'] = node.var_name + self.setbase(node) + + def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None: + self._accept('value', node.value) + self.current['var_name'] = node.var_name + self.setbase(node) + + def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None: + self._accept('items', node.items) + self._accept('block', node.block) + self.current['varnames'] = node.varnames + self.setbase(node) + + def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None: + self._accept_list('ifs', node.ifs) + self._accept('else', node.elseblock) + self.setbase(node) + + def visit_UMinusNode(self, node: mparser.UMinusNode) -> None: + self._accept('right', node.value) + self.setbase(node) + + def visit_IfNode(self, node: mparser.IfNode) -> None: + self._accept('condition', node.condition) + self._accept('block', node.block) + self.setbase(node) + + def visit_TernaryNode(self, node: mparser.TernaryNode) -> None: + self._accept('condition', node.condition) + self._accept('true', node.trueblock) + self._accept('false', node.falseblock) + self.setbase(node) + + def visit_ArgumentNode(self, node: mparser.ArgumentNode) -> None: + self._accept_list('positional', node.arguments) + kwargs_list = [] # type: T.List[T.Dict[str, T.Dict[str, T.Any]]] + for key, val in node.kwargs.items(): + key_res = {} # type: T.Dict[str, T.Any] + val_res = {} # type: T.Dict[str, T.Any] + self._raw_accept(key, key_res) + self._raw_accept(val, val_res) + kwargs_list += [{'key': key_res, 'val': val_res}] + self.current['kwargs'] = kwargs_list + self.setbase(node) diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py index 37be463..451020d 100644 --- a/mesonbuild/ast/visitor.py +++ b/mesonbuild/ast/visitor.py @@ -113,8 +113,7 @@ class AstVisitor: self.visit_default_func(node) for i in node.ifs: i.accept(self) - if node.elseblock: - node.elseblock.accept(self) + node.elseblock.accept(self) def visit_UMinusNode(self, node: mparser.UMinusNode) -> None: self.visit_default_func(node) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 31ddfb4..840c9a3 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -12,23 +12,54 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os, pickle, re +from collections import OrderedDict +from functools import lru_cache +import enum +import json +import os +import pickle +import re +import shlex +import subprocess import textwrap +import typing as T + from .. import build from .. import dependencies from .. import mesonlib from .. import mlog -import json -import subprocess -from ..mesonlib import MachineChoice, MesonException, OrderedSet, OptionOverrideProxy -from ..mesonlib import classify_unity_sources, unholder -from ..mesonlib import File from ..compilers import CompilerArgs, VisualStudioLikeCompiler -from ..interpreter import Interpreter -from collections import OrderedDict -import shlex -from functools import lru_cache -import typing as T +from ..mesonlib import ( + File, MachineChoice, MesonException, OrderedSet, OptionOverrideProxy, + classify_unity_sources, unholder +) + +if T.TYPE_CHECKING: + from ..interpreter import Interpreter + + +class TestProtocol(enum.Enum): + + EXITCODE = 0 + TAP = 1 + GTEST = 2 + + @classmethod + def from_str(cls, string: str) -> 'TestProtocol': + if string == 'exitcode': + return cls.EXITCODE + elif string == 'tap': + return cls.TAP + elif string == 'gtest': + return cls.GTEST + raise MesonException('unknown test format {}'.format(string)) + + def __str__(self) -> str: + if self is self.EXITCODE: + return 'exitcode' + elif self is self.GTEST: + return 'gtest' + return 'tap' class CleanTrees: @@ -59,12 +90,13 @@ class InstallData: self.mesonintrospect = mesonintrospect class TargetInstallData: - def __init__(self, fname, outdir, aliases, strip, install_name_mappings, install_rpath, install_mode, optional=False): + def __init__(self, fname, outdir, aliases, strip, install_name_mappings, rpath_dirs_to_remove, install_rpath, install_mode, optional=False): self.fname = fname self.outdir = outdir self.aliases = aliases self.strip = strip self.install_name_mappings = install_name_mappings + self.rpath_dirs_to_remove = rpath_dirs_to_remove self.install_rpath = install_rpath self.install_mode = install_mode self.optional = optional @@ -83,11 +115,12 @@ class ExecutableSerialisation: class TestSerialisation: def __init__(self, name: str, project: str, suite: str, fname: T.List[str], - is_cross_built: bool, exe_wrapper: T.Optional[build.Executable], + is_cross_built: bool, exe_wrapper: T.Optional[dependencies.ExternalProgram], needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables, should_fail: bool, timeout: T.Optional[int], workdir: T.Optional[str], - extra_paths: T.List[str], protocol: str, priority: int): + extra_paths: T.List[str], protocol: TestProtocol, priority: int, + cmd_is_built: bool): self.name = name self.project_name = project self.suite = suite @@ -106,8 +139,10 @@ class TestSerialisation: self.protocol = protocol self.priority = priority self.needs_exe_wrapper = needs_exe_wrapper + self.cmd_is_built = cmd_is_built + -def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional[Interpreter] = None) -> T.Optional['Backend']: +def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']: if backend == 'ninja': from . import ninjabackend return ninjabackend.NinjaBackend(build, interpreter) @@ -134,7 +169,7 @@ def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, i # This class contains the basic functionality that is needed by all backends. # Feel free to move stuff in and out of it as you see fit. class Backend: - def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]): + def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']): # Make it possible to construct a dummy backend # This is used for introspection without a build directory if build is None: @@ -196,7 +231,7 @@ class Backend: return os.path.join(self.get_target_dir(target), target.get_filename()) elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)): if not target.is_linkable_target(): - raise MesonException('Tried to link against custom target "%s", which is not linkable.' % target.name) + raise MesonException('Tried to link against custom target "{}", which is not linkable.'.format(target.name)) return os.path.join(self.get_target_dir(target), target.get_filename()) elif isinstance(target, build.Executable): if target.import_filename: @@ -227,7 +262,7 @@ class Backend: return self.build_to_src def get_target_private_dir(self, target): - return os.path.join(self.get_target_dir(target), target.get_id()) + return os.path.join(self.get_target_filename(target) + '.p') def get_target_private_dir_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) @@ -282,7 +317,7 @@ class Backend: ofile = init_language_file(comp.get_default_suffix(), unity_file_number) unity_file_number += 1 files_in_current = 0 - ofile.write('#include<%s>\n' % src) + ofile.write('#include<{}>\n'.format(src)) files_in_current += 1 if ofile: ofile.close() @@ -412,6 +447,21 @@ class Backend: return True return False + def get_external_rpath_dirs(self, target): + dirs = set() + args = [] + # FIXME: is there a better way? + for lang in ['c', 'cpp']: + try: + args.extend(self.environment.coredata.get_external_link_args(target.for_machine, lang)) + except Exception: + pass + for arg in args: + if arg.startswith('-Wl,-rpath='): + for dir in arg.replace('-Wl,-rpath=','').split(':'): + dirs.add(dir) + return dirs + def rpaths_for_bundled_shared_libraries(self, target, exclude_system=True): paths = [] for dep in target.external_deps: @@ -426,6 +476,9 @@ class Backend: if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): # No point in adding system paths. continue + # Don't remove rpaths specified in LDFLAGS. + if libdir in self.get_external_rpath_dirs(target): + continue # Windows doesn't support rpaths, but we use this function to # emulate rpaths by setting PATH, so also accept DLLs here if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']: @@ -445,6 +498,7 @@ class Backend: result = OrderedSet() result.add('meson-out') result.update(self.rpaths_for_bundled_shared_libraries(target)) + target.rpath_dirs_to_remove.update([d.encode('utf8') for d in result]) return tuple(result) def object_filename_from_source(self, target, source): @@ -537,14 +591,14 @@ class Backend: def create_msvc_pch_implementation(self, target, lang, pch_header): # We have to include the language in the file name, otherwise # pch.c and pch.cpp will both end up as pch.obj in VS backends. - impl_name = 'meson_pch-%s.%s' % (lang, lang) + impl_name = 'meson_pch-{}.{}'.format(lang, lang) pch_rel_to_build = os.path.join(self.get_target_private_dir(target), impl_name) # Make sure to prepend the build dir, since the working directory is # not defined. Otherwise, we might create the file in the wrong path. pch_file = os.path.join(self.build_dir, pch_rel_to_build) os.makedirs(os.path.dirname(pch_file), exist_ok=True) - content = '#include "%s"' % os.path.basename(pch_header) + content = '#include "{}"'.format(os.path.basename(pch_header)) pch_file_tmp = pch_file + '.tmp' with open(pch_file_tmp, 'w') as f: f.write(content) @@ -664,7 +718,7 @@ class Backend: args = [] for d in deps: if not (d.is_linkable_target()): - raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename()) + raise RuntimeError('Tried to link with a non-library target "{}".'.format(d.get_basename())) arg = self.get_target_filename_for_linking(d) if not arg: continue @@ -737,7 +791,16 @@ class Backend: # E.g. an external verifier or simulator program run on a generated executable. # Can always be run without a wrapper. test_for_machine = MachineChoice.BUILD - is_cross = not self.environment.machines.matches_build_machine(test_for_machine) + + # we allow passing compiled executables to tests, which may be cross built. + # We need to consider these as well when considering whether the target is cross or not. + for a in t.cmd_args: + if isinstance(a, build.BuildTarget): + if a.for_machine is MachineChoice.HOST: + test_for_machine = MachineChoice.HOST + break + + is_cross = self.environment.is_cross_build(test_for_machine) if is_cross and self.environment.need_exe_wrapper(): exe_wrapper = self.environment.get_exe_wrapper() else: @@ -750,6 +813,7 @@ class Backend: extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps) else: extra_paths = [] + cmd_args = [] for a in unholder(t.cmd_args): if isinstance(a, build.BuildTarget): @@ -759,6 +823,11 @@ class Backend: cmd_args.append(a) elif isinstance(a, str): cmd_args.append(a) + elif isinstance(a, build.Executable): + p = self.construct_target_rel_path(a, t.workdir) + if p == a.get_filename(): + p = './' + p + cmd_args.append(p) elif isinstance(a, build.Target): cmd_args.append(self.construct_target_rel_path(a, t.workdir)) else: @@ -767,7 +836,8 @@ class Backend: exe_wrapper, self.environment.need_exe_wrapper(), t.is_parallel, cmd_args, t.env, t.should_fail, t.timeout, t.workdir, - extra_paths, t.protocol, t.priority) + extra_paths, t.protocol, t.priority, + isinstance(exe, build.Executable)) arr.append(ts) return arr @@ -853,7 +923,7 @@ class Backend: m = regex.search(arg) while m is not None: index = int(m.group(1)) - src = '@OUTPUT%d@' % index + src = '@OUTPUT{}@'.format(index) arg = arg.replace(src, os.path.join(private_dir, output_list[index])) m = regex.search(arg) newargs.append(arg) @@ -1109,6 +1179,7 @@ class Backend: mappings = t.get_link_deps_mapping(d.prefix, self.environment) i = TargetInstallData(self.get_target_filename(t), outdirs[0], t.get_aliases(), should_strip, mappings, + t.rpath_dirs_to_remove, t.install_rpath, install_mode) d.targets.append(i) @@ -1126,14 +1197,14 @@ class Backend: implib_install_dir = self.environment.get_import_lib_dir() # Install the import library; may not exist for shared modules i = TargetInstallData(self.get_target_filename_for_linking(t), - implib_install_dir, {}, False, {}, '', install_mode, + implib_install_dir, {}, False, {}, set(), '', install_mode, optional=isinstance(t, build.SharedModule)) d.targets.append(i) if not should_strip and t.get_debug_filename(): debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename()) i = TargetInstallData(debug_file, outdirs[0], - {}, False, {}, '', + {}, False, {}, set(), '', install_mode, optional=True) d.targets.append(i) # Install secondary outputs. Only used for Vala right now. @@ -1143,7 +1214,7 @@ class Backend: if outdir is False: continue f = os.path.join(self.get_target_dir(t), output) - i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode) + i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode) d.targets.append(i) elif isinstance(t, build.CustomTarget): # If only one install_dir is specified, assume that all @@ -1156,7 +1227,7 @@ class Backend: if num_outdirs == 1 and num_out > 1: for output in t.get_outputs(): f = os.path.join(self.get_target_dir(t), output) - i = TargetInstallData(f, outdirs[0], {}, False, {}, None, install_mode, + i = TargetInstallData(f, outdirs[0], {}, False, {}, set(), None, install_mode, optional=not t.build_by_default) d.targets.append(i) else: @@ -1165,7 +1236,7 @@ class Backend: if outdir is False: continue f = os.path.join(self.get_target_dir(t), output) - i = TargetInstallData(f, outdir, {}, False, {}, None, install_mode, + i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode, optional=not t.build_by_default) d.targets.append(i) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e765466..69e7618 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -67,9 +67,9 @@ def ninja_quote(text, is_build_line=False): if '\n' in text: errmsg = '''Ninja does not support newlines in rules. The content was: -%s +{} -Please report this error with a test case to the Meson bug tracker.''' % text +Please report this error with a test case to the Meson bug tracker.'''.format(text) raise MesonException(errmsg) return text @@ -101,18 +101,18 @@ class NinjaRule: if not self.refcount: return - outfile.write('rule %s\n' % self.name) + outfile.write('rule {}\n'.format(self.name)) if self.rspable: - outfile.write(' command = %s @$out.rsp\n' % ' '.join(self.command)) + outfile.write(' command = {} @$out.rsp\n'.format(' '.join(self.command))) outfile.write(' rspfile = $out.rsp\n') - outfile.write(' rspfile_content = %s\n' % ' '.join(self.args)) + outfile.write(' rspfile_content = {}\n'.format(' '.join(self.args))) else: - outfile.write(' command = %s\n' % ' '.join(self.command + self.args)) + outfile.write(' command = {}\n'.format(' '.join(self.command + self.args))) if self.deps: - outfile.write(' deps = %s\n' % self.deps) + outfile.write(' deps = {}\n'.format(self.deps)) if self.depfile: - outfile.write(' depfile = %s\n' % self.depfile) - outfile.write(' description = %s\n' % self.description) + outfile.write(' depfile = {}\n'.format(self.depfile)) + outfile.write(' description = {}\n'.format(self.description)) if self.extra: for l in self.extra.split('\n'): outfile.write(' ') @@ -151,6 +151,10 @@ class NinjaBuildElement: self.orderdeps.add(dep) def add_item(self, name, elems): + # Always convert from GCC-style argument naming to the naming used by the + # current compiler. Also filter system include paths, deduplicate, etc. + if isinstance(elems, CompilerArgs): + elems = elems.to_native() if isinstance(elems, str): elems = [elems] self.elems.append((name, elems)) @@ -185,7 +189,7 @@ class NinjaBuildElement: for e in self.elems: (name, elems) = e should_quote = name not in raw_names - line = ' %s = ' % name + line = ' {} = '.format(name) newelems = [] for i in elems: if not should_quote or i == '&&': # Hackety hack hack @@ -204,7 +208,7 @@ class NinjaBuildElement: def check_outputs(self): for n in self.outfilenames: if n in self.all_outputs: - raise MesonException('Multiple producers for Ninja target "%s". Please rename your targets.' % n) + raise MesonException('Multiple producers for Ninja target "{}". Please rename your targets.'.format(n)) self.all_outputs[n] = True class NinjaBackend(backends.Backend): @@ -299,8 +303,7 @@ int dummy; outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename) tempfilename = outfilename + '~' with open(tempfilename, 'w', encoding='utf-8') as outfile: - outfile.write('# This is the build file for project "%s"\n' % - self.build.get_project()) + outfile.write('# This is the build file for project "{}"\n'.format(self.build.get_project())) outfile.write('# It is autogenerated by the Meson build system.\n') outfile.write('# Do not edit by hand.\n\n') outfile.write('ninja_required_version = 1.7.1\n\n') @@ -308,9 +311,9 @@ int dummy; num_pools = self.environment.coredata.backend_options['backend_max_links'].value if num_pools > 0: outfile.write('''pool link_pool - depth = %d + depth = {} -''' % num_pools) +'''.format(num_pools)) with self.detect_vs_dep_prefix(tempfilename) as outfile: self.generate_rules() @@ -571,7 +574,7 @@ int dummy; generated_source_files.append(raw_src) elif self.environment.is_object(rel_src): obj_list.append(rel_src) - elif self.environment.is_library(rel_src): + elif self.environment.is_library(rel_src) or modules.is_module_library(rel_src): pass else: # Assume anything not specifically a source file is a header. This is because @@ -586,7 +589,7 @@ int dummy; o = self.generate_llvm_ir_compile(target, src) else: o = self.generate_single_compile(target, src, True, - header_deps=header_deps) + order_deps=header_deps) obj_list.append(o) use_pch = self.environment.coredata.base_options.get('b_pch', False) @@ -765,7 +768,7 @@ int dummy; target_name = 'meson-{}'.format(self.build_run_target_name(target)) elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', []) elem.add_item('COMMAND', cmd) - elem.add_item('description', 'Running external command %s' % target.name) + elem.add_item('description', 'Running external command {}'.format(target.name)) elem.add_item('pool', 'console') # Alias that runs the target defined above with the name the user specified self.create_target_alias(target_name) @@ -980,12 +983,12 @@ int dummy; ofilename = os.path.join(self.get_target_private_dir(target), ofilebase) elem = NinjaBuildElement(self.all_outputs, ofilename, "CUSTOM_COMMAND", rel_sourcefile) elem.add_item('COMMAND', ['resgen', rel_sourcefile, ofilename]) - elem.add_item('DESC', 'Compiling resource %s' % rel_sourcefile) + elem.add_item('DESC', 'Compiling resource {}'.format(rel_sourcefile)) self.add_build(elem) deps.append(ofilename) a = '-resource:' + ofilename else: - raise InvalidArguments('Unknown resource file %s.' % r) + raise InvalidArguments('Unknown resource file {}.'.format(r)) args.append(a) return args, deps @@ -1278,7 +1281,7 @@ int dummy; main_rust_file = None for i in target.get_sources(): if not rustc.can_compile(i): - raise InvalidArguments('Rust target %s contains a non-rust source file.' % target.get_basename()) + raise InvalidArguments('Rust target {} contains a non-rust source file.'.format(target.get_basename())) if main_rust_file is None: main_rust_file = i.rel_to_builddir(self.build_to_src) if main_rust_file is None: @@ -1349,7 +1352,8 @@ int dummy; self.get_target_dir(target)) else: target_slashname_workaround_dir = self.get_target_dir(target) - rpath_args = rustc.build_rpath_args(self.environment, + (rpath_args, target.rpath_dirs_to_remove) = \ + rustc.build_rpath_args(self.environment, self.environment.get_build_dir(), target_slashname_workaround_dir, self.determine_rpath_dirs(target), @@ -1377,11 +1381,11 @@ int dummy; @classmethod def get_compiler_rule_name(cls, lang: str, for_machine: MachineChoice) -> str: - return '%s_COMPILER%s' % (lang, cls.get_rule_suffix(for_machine)) + return '{}_COMPILER{}'.format(lang, cls.get_rule_suffix(for_machine)) @classmethod def get_pch_rule_name(cls, lang: str, for_machine: MachineChoice) -> str: - return '%s_PCH%s' % (lang, cls.get_rule_suffix(for_machine)) + return '{}_PCH{}'.format(lang, cls.get_rule_suffix(for_machine)) @classmethod def compiler_to_rule_name(cls, compiler: Compiler) -> str: @@ -1453,7 +1457,7 @@ int dummy; abs_headers.append(absh) header_imports += swiftc.get_header_import_args(absh) else: - raise InvalidArguments('Swift target %s contains a non-swift source file.' % target.get_basename()) + raise InvalidArguments('Swift target {} contains a non-swift source file.'.format(target.get_basename())) os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) compile_args = swiftc.get_compile_only_args() compile_args += swiftc.get_optimization_args(self.get_option_for_target('optimization', target)) @@ -1540,7 +1544,7 @@ int dummy; static_linker = self.build.static_linker[for_machine] if static_linker is None: return - rule = 'STATIC_LINKER%s' % self.get_rule_suffix(for_machine) + rule = 'STATIC_LINKER{}'.format(self.get_rule_suffix(for_machine)) cmdlist = [] args = ['$in'] # FIXME: Must normalize file names with pathlib.Path before writing @@ -1574,7 +1578,7 @@ int dummy; or langname == 'rust' \ or langname == 'cs': continue - rule = '%s_LINKER%s' % (langname, self.get_rule_suffix(for_machine)) + rule = '{}_LINKER{}'.format(langname, self.get_rule_suffix(for_machine)) command = compiler.get_linker_exelist() args = ['$ARGS'] + compiler.get_linker_output_args('$out') + ['$in', '$LINK_ARGS'] description = 'Linking target $out' @@ -1645,7 +1649,7 @@ int dummy; self.add_rule(NinjaRule(rule, command, [], description)) def generate_fortran_dep_hack(self, crstr): - rule = 'FORTRAN_DEP_HACK%s' % (crstr) + rule = 'FORTRAN_DEP_HACK{}'.format(crstr) if mesonlib.is_windows(): cmd = ['cmd', '/C'] else: @@ -1698,7 +1702,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) command = [ninja_quote(i) for i in compiler.get_exelist()] args = ['$ARGS'] + quoted_depargs + compiler.get_output_args('$out') + compiler.get_compile_only_args() + ['$in'] - description = 'Compiling %s object $out' % compiler.get_display_language() + description = 'Compiling {} object $out'.format(compiler.get_display_language()) if isinstance(compiler, VisualStudioLikeCompiler): deps = 'msvc' depfile = None @@ -1859,9 +1863,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) modname = modmatch.group(1).lower() if modname in module_files: raise InvalidArguments( - 'Namespace collision: module %s defined in ' - 'two files %s and %s.' % - (modname, module_files[modname], s)) + 'Namespace collision: module {} defined in ' + 'two files {} and {}.'.format(modname, module_files[modname], s)) module_files[modname] = s else: submodmatch = submodre.match(line) @@ -1872,9 +1875,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if submodname in submodule_files: raise InvalidArguments( - 'Namespace collision: submodule %s defined in ' - 'two files %s and %s.' % - (submodname, submodule_files[submodname], s)) + 'Namespace collision: submodule {} defined in ' + 'two files {} and {}.'.format(submodname, submodule_files[submodname], s)) submodule_files[submodname] = s self.fortran_deps[target.get_basename()] = {**module_files, **submodule_files} @@ -1987,9 +1989,6 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # Write the Ninja build command compiler_name = self.get_compiler_rule_name('llvm_ir', compiler.for_machine) element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src) - # Convert from GCC-style link argument naming to the naming used by the - # current compiler. - commands = commands.to_native() element.add_item('ARGS', commands) self.add_build(element) return rel_obj @@ -2206,9 +2205,6 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) d = os.path.join(self.get_target_private_dir(target), d) element.add_orderdep(d) element.add_dep(pch_dep) - # Convert from GCC-style link argument naming to the naming used by the - # current compiler. - commands = commands.to_native() for i in self.get_fortran_orderdeps(target, compiler): element.add_orderdep(i) element.add_item('DEPFILE', dep_file) @@ -2583,20 +2579,19 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.get_target_dir(target)) else: target_slashname_workaround_dir = self.get_target_dir(target) - commands += linker.build_rpath_args(self.environment, + (rpath_args, target.rpath_dirs_to_remove) = \ + linker.build_rpath_args(self.environment, self.environment.get_build_dir(), target_slashname_workaround_dir, self.determine_rpath_dirs(target), target.build_rpath, target.install_rpath) + commands += rpath_args # Add libraries generated by custom targets custom_target_libraries = self.get_custom_target_provided_libraries(target) commands += extra_args commands += custom_target_libraries commands += stdlib_args # Standard library arguments go last, because they never depend on anything. - # Convert from GCC-style link argument naming to the naming used by the - # current compiler. - commands = commands.to_native() dep_targets.extend([self.get_dependency_filename(t) for t in dependencies]) dep_targets.extend([self.get_dependency_filename(t) for t in target.link_depends]) @@ -2647,18 +2642,14 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def generate_gcov_clean(self): gcno_elem = NinjaBuildElement(self.all_outputs, 'meson-clean-gcno', 'CUSTOM_COMMAND', 'PHONY') - script_root = self.environment.get_script_dir() - clean_script = os.path.join(script_root, 'delwithsuffix.py') - gcno_elem.add_item('COMMAND', mesonlib.python_command + [clean_script, '.', 'gcno']) + gcno_elem.add_item('COMMAND', mesonlib.meson_command + ['--internal', 'delwithsuffix', '.', 'gcno']) gcno_elem.add_item('description', 'Deleting gcno files') self.add_build(gcno_elem) # Alias that runs the target defined above self.create_target_alias('meson-clean-gcno') gcda_elem = NinjaBuildElement(self.all_outputs, 'meson-clean-gcda', 'CUSTOM_COMMAND', 'PHONY') - script_root = self.environment.get_script_dir() - clean_script = os.path.join(script_root, 'delwithsuffix.py') - gcda_elem.add_item('COMMAND', mesonlib.python_command + [clean_script, '.', 'gcda']) + gcda_elem.add_item('COMMAND', mesonlib.meson_command + ['--internal', 'delwithsuffix', '.', 'gcda']) gcda_elem.add_item('description', 'Deleting gcda files') self.add_build(gcda_elem) # Alias that runs the target defined above diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 80ff910..6965c42 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -98,6 +98,9 @@ class Vs2010Backend(backends.Backend): self.subdirs = {} self.handled_target_deps = {} + def get_target_private_dir(self, target): + return os.path.join(self.get_target_dir(target), target.get_id()) + def generate_custom_generator_commands(self, target, parent_node): generator_output_files = [] custom_target_include_dirs = [] @@ -821,12 +824,12 @@ class Vs2010Backend(backends.Backend): clconf = ET.SubElement(compiles, 'ClCompile') # CRT type; debug or release if vscrt_type.value == 'from_buildtype': - if self.buildtype == 'debug' or self.buildtype == 'debugoptimized': + if self.buildtype == 'debug': ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL' else: ET.SubElement(type_config, 'UseDebugLibraries').text = 'false' - ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded' + ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL' elif vscrt_type.value == 'mdd': ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL' @@ -1192,7 +1195,8 @@ class Vs2010Backend(backends.Backend): # /nologo ET.SubElement(link, 'SuppressStartupBanner').text = 'true' # /release - ET.SubElement(link, 'SetChecksum').text = 'true' + if not self.environment.coredata.get_builtin_option('debug'): + ET.SubElement(link, 'SetChecksum').text = 'true' meson_file_group = ET.SubElement(root, 'ItemGroup') ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename)) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c200261..d7f3b66 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -12,12 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy, os, re from collections import OrderedDict, defaultdict -import itertools, pathlib +from functools import lru_cache +import copy import hashlib +import itertools, pathlib +import os import pickle -from functools import lru_cache +import re import typing as T from . import environment @@ -82,6 +84,7 @@ buildtarget_kwargs = set([ 'override_options', 'sources', 'gnu_symbol_visibility', + 'link_language', ]) known_build_target_kwargs = ( @@ -92,7 +95,7 @@ known_build_target_kwargs = ( rust_kwargs | cs_kwargs) -known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'link_language', 'pie'} +known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'} known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'} known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'} known_stlib_kwargs = known_build_target_kwargs | {'pic'} @@ -494,6 +497,7 @@ class BuildTarget(Target): self.link_targets = [] self.link_whole_targets = [] self.link_depends = [] + self.added_deps = set() self.name_prefix_set = False self.name_suffix_set = False self.filename = 'no_name' @@ -508,6 +512,8 @@ class BuildTarget(Target): self.d_features = {} self.pic = False self.pie = False + # Track build_rpath entries so we can remove them at install time + self.rpath_dirs_to_remove = set() # Sources can be: # 1. Pre-existing source files in the source tree # 2. Pre-existing sources generated by configure_file in the build tree @@ -531,6 +537,9 @@ class BuildTarget(Target): repr_str = "<{0} {1}: {2}>" return repr_str.format(self.__class__.__name__, self.get_id(), self.filename) + def __str__(self): + return "{}".format(self.name) + def validate_install(self, environment): if self.for_machine is MachineChoice.BUILD and self.need_install: if environment.is_cross_build(): @@ -726,7 +735,7 @@ class BuildTarget(Target): File.from_source_file(environment.source_dir, self.subdir, s)) elif hasattr(s, 'get_outputs'): self.link_depends.extend( - [File.from_built_file(s.subdir, p) for p in s.get_outputs()]) + [File.from_built_file(s.get_subdir(), p) for p in s.get_outputs()]) else: raise InvalidArguments( 'Link_depends arguments must be strings, Files, ' @@ -769,7 +778,7 @@ class BuildTarget(Target): if isinstance(src, str): src = File(False, self.subdir, src) elif isinstance(src, File): - FeatureNew('File argument for extract_objects', '0.50.0').use(self.subproject) + FeatureNew.single_use('File argument for extract_objects', '0.50.0', self.subproject) else: raise MesonException('Object extraction arguments must be strings or Files.') # FIXME: It could be a generated source @@ -1050,6 +1059,8 @@ This will become a hard error in a future Meson release.''') def add_deps(self, deps): deps = listify(deps) for dep in unholder(deps): + if dep in self.added_deps: + continue if isinstance(dep, dependencies.InternalDependency): # Those parts that are internal. self.process_sourcelist(dep.sources) @@ -1088,6 +1099,7 @@ You probably should put it in link_with instead.''') 'either an external dependency (returned by find_library() or ' 'dependency()) or an internal dependency (returned by ' 'declare_dependency()).'.format(type(dep).__name__)) + self.added_deps.add(dep) def get_external_deps(self): return self.external_deps @@ -1104,7 +1116,7 @@ You probably should put it in link_with instead.''') if not isinstance(t, (Target, CustomTargetIndex)): raise InvalidArguments('{!r} is not a target.'.format(t)) if not t.is_linkable_target(): - raise InvalidArguments('Link target {!r} is not linkable.'.format(t)) + raise InvalidArguments("Link target '{!s}' is not linkable.".format(t)) if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic: msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name) msg += "Use the 'pic' option to static_library to build with PIC." @@ -1217,11 +1229,7 @@ You probably should put it in link_with instead.''') See: https://github.com/mesonbuild/meson/issues/1653 ''' - langs = [] - - # User specified link_language of target (for multi-language targets) - if self.link_language: - return [self.link_language] + langs = [] # type: T.List[str] # Check if any of the external libraries were written in this language for dep in self.external_deps: @@ -1253,6 +1261,12 @@ You probably should put it in link_with instead.''') # Populate list of all compilers, not just those being used to compile # sources in this target all_compilers = self.environment.coredata.compilers[self.for_machine] + + # If the user set the link_language, just return that. + if self.link_language: + comp = all_compilers[self.link_language] + return comp, comp.language_stdlib_only_link_flags() + # Languages used by dependencies dep_langs = self.get_langs_used_by_deps() # Pick a compiler based on the language priority-order @@ -2148,7 +2162,7 @@ class CustomTarget(Target): 'when installing a target') if isinstance(kwargs['install_dir'], list): - FeatureNew('multiple install_dir for custom_target', '0.40.0').use(self.subproject) + FeatureNew.single_use('multiple install_dir for custom_target', '0.40.0', self.subproject) # If an item in this list is False, the output corresponding to # the list index of that item will not be installed self.install_dir = typeslistify(kwargs['install_dir'], (str, bool)) @@ -2160,7 +2174,6 @@ class CustomTarget(Target): if 'build_always' in kwargs and 'build_always_stale' in kwargs: raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.') elif 'build_always' in kwargs: - mlog.deprecation('build_always is deprecated. Combine build_by_default and build_always_stale instead.') if 'build_by_default' not in kwargs: self.build_by_default = kwargs['build_always'] self.build_always_stale = kwargs['build_always'] diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index 66713a1..adc028c 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -132,7 +132,7 @@ class CMakeExecutor: msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.' mlog.warning(msg) return None - cmvers = re.sub(r'\s*(cmake|cmake3) version\s*', '', out.split('\n')[0]).strip() + cmvers = re.search(r'(cmake|cmake3)\s*version\s*([\d.]+)', out).group(2) return cmvers def set_exec_mode(self, print_cmout: T.Optional[bool] = None, always_capture_stderr: T.Optional[bool] = None) -> None: diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 125f18b..09b633e 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -289,7 +289,17 @@ class ConverterTarget: for j in self.compile_opts[i]: m = ConverterTarget.std_regex.match(j) if m: - self.override_options += ['{}_std={}'.format(i, m.group(2))] + std = m.group(2) + supported = self._all_lang_stds(i) + if std not in supported: + mlog.warning( + 'Unknown {0}_std "{1}" -> Ignoring. Try setting the project-' + 'level {0}_std if build errors occur. Known ' + '{0}_stds are: {2}'.format(i, std, ' '.join(supported)), + once=True + ) + continue + self.override_options += ['{}_std={}'.format(i, std)] elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']: self.pie = True elif j in blacklist_compiler_flags: @@ -345,9 +355,16 @@ class ConverterTarget: if 'CONFIGURATIONS' in tgt.properties: cfgs += [x for x in tgt.properties['CONFIGURATIONS'] if x] cfg = cfgs[0] - - if 'RELEASE' in cfgs: - cfg = 'RELEASE' + + is_debug = self.env.coredata.get_builtin_option('debug'); + if is_debug: + if 'DEBUG' in cfgs: + cfg = 'DEBUG' + elif 'RELEASE' in cfgs: + cfg = 'RELEASE' + else: + if 'RELEASE' in cfgs: + cfg = 'RELEASE' if 'IMPORTED_IMPLIB_{}'.format(cfg) in tgt.properties: libraries += [x for x in tgt.properties['IMPORTED_IMPLIB_{}'.format(cfg)] if x] @@ -539,6 +556,13 @@ class ConverterTarget: suffixes += [x for x in exts] return suffixes + @lru_cache(maxsize=None) + def _all_lang_stds(self, lang: str) -> T.List[str]: + lang_opts = self.env.coredata.compiler_options.build.get(lang, None) + if not lang_opts or 'std' not in lang_opts: + return [] + return lang_opts['std'].choices + def process_inter_target_dependencies(self): # Move the dependencies from all transfer_dependencies_from to the target to_process = list(self.depends) diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 0aee3fa..f20bcc8 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -660,25 +660,40 @@ class CMakeTraceParser: fixed_list = [] # type: T.List[str] curr_str = None # type: T.Optional[str] + path_found = False # type: bool for i in broken_list: if curr_str is None: curr_str = i + path_found = False elif os.path.isfile(curr_str): # Abort concatenation if curr_str is an existing file fixed_list += [curr_str] curr_str = i + path_found = False elif not reg_start.match(curr_str): # Abort concatenation if curr_str no longer matches the regex fixed_list += [curr_str] curr_str = i - elif reg_end.match(i) or os.path.exists('{} {}'.format(curr_str, i)): + path_found = False + elif reg_end.match(i): # File detected curr_str = '{} {}'.format(curr_str, i) fixed_list += [curr_str] curr_str = None + path_found = False + elif os.path.exists('{} {}'.format(curr_str, i)): + # Path detected + curr_str = '{} {}'.format(curr_str, i) + path_found = True + elif path_found: + # Add path to fixed_list after ensuring the whole path is in curr_str + fixed_list += [curr_str] + curr_str = i + path_found = False else: curr_str = '{} {}'.format(curr_str, i) + path_found = False if curr_str: fixed_list += [curr_str] diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 1bc9e84..aac99b4 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -86,9 +86,10 @@ class ClangCCompiler(ClangCompiler, CCompiler): _C18_VERSION = '>=8.0.0' def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): + is_cross, info: 'MachineInfo', exe_wrapper=None, + defines: T.Optional[T.List[str]] = None, **kwargs): CCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self) + ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], '1': default_warn_args, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 3d3a503..b0fa5f5 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -52,7 +52,7 @@ lib_suffixes = ('a', 'lib', 'dll', 'dll.a', 'dylib', 'so') # This means we can't include .h headers here since they could be C, C++, ObjC, etc. lang_suffixes = { 'c': ('c',), - 'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx'), + 'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx', 'ino'), 'cuda': ('cu',), # f90, f95, f03, f08 are for free-form fortran ('f90' recommended) # f, for, ftn, fpp are for fixed-form fortran ('f' or 'for' recommended) @@ -186,7 +186,7 @@ rust_buildtype_args = {'plain': [], d_gdc_buildtype_args = {'plain': [], 'debug': [], 'debugoptimized': ['-finline-functions'], - 'release': ['-frelease', '-finline-functions'], + 'release': ['-finline-functions'], 'minsize': [], 'custom': [], } @@ -194,7 +194,7 @@ d_gdc_buildtype_args = {'plain': [], d_ldc_buildtype_args = {'plain': [], 'debug': [], 'debugoptimized': ['-enable-inlining', '-Hkeep-all-bodies'], - 'release': ['-release', '-enable-inlining', '-Hkeep-all-bodies'], + 'release': ['-enable-inlining', '-Hkeep-all-bodies'], 'minsize': [], 'custom': [], } @@ -202,7 +202,7 @@ d_ldc_buildtype_args = {'plain': [], d_dmd_buildtype_args = {'plain': [], 'debug': [], 'debugoptimized': ['-inline'], - 'release': ['-release', '-inline'], + 'release': ['-inline'], 'minsize': [], 'custom': [], } @@ -320,7 +320,7 @@ def get_base_compile_args(options, compiler): if (options['b_ndebug'].value == 'true' or (options['b_ndebug'].value == 'if-release' and options['buildtype'].value in {'release', 'plain'})): - args += ['-DNDEBUG'] + args += compiler.get_disable_assert_args() except KeyError: pass # This does not need a try...except @@ -494,7 +494,7 @@ class CompilerArgs(collections.abc.MutableSequence): value = self.__container[index] del self.__container[index] if value in self.__seen_args and value in self.__container: # this is also honoring that you can have duplicated entries - self.__seen_args.remove(value) + self.__seen_args.remove(value) def __len__(self) -> int: return len(self.__container) @@ -688,7 +688,7 @@ class CompilerArgs(collections.abc.MutableSequence): should_prepend = self._should_prepend(arg) if dedup == 2: # Remove all previous occurrences of the arg and add it anew - if arg in self.__seen_args and arg not in this_round_added: #if __seen_args contains arg as well as this_round_added, then its not yet part in self. + if arg in self.__seen_args and arg not in this_round_added: # if __seen_args contains arg as well as this_round_added, then its not yet part in self. self.remove(arg) if should_prepend: if arg in pre: @@ -954,7 +954,7 @@ class Compiler: return args @contextlib.contextmanager - def compile(self, code, extra_args=None, *, mode='link', want_output=False, temp_dir=None): + def compile(self, code: str, extra_args: list = None, *, mode: str = 'link', want_output: bool = False, temp_dir: str = None): if extra_args is None: extra_args = [] try: @@ -1077,7 +1077,7 @@ class Compiler: def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: return self.linker.build_rpath_args( env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) @@ -1136,7 +1136,7 @@ class Compiler: def remove_linkerlike_args(self, args): rm_exact = ('-headerpad_max_install_names',) rm_prefixes = ('-Wl,', '-L',) - rm_next = ('-L',) + rm_next = ('-L', '-framework',) ret = [] iargs = iter(args) for arg in iargs: @@ -1204,6 +1204,9 @@ class Compiler: def get_coverage_link_args(self) -> T.List[str]: return self.linker.get_coverage_args() + def get_disable_assert_args(self) -> T.List[str]: + return [] + def get_largefile_args(compiler): ''' diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index d30017f..478a68c 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -155,10 +155,11 @@ class CPPCompiler(CLikeCompiler, Compiler): class ClangCPPCompiler(ClangCompiler, CPPCompiler): def __init__(self, exelist, version, for_machine: MachineChoice, - is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): + is_cross, info: 'MachineInfo', exe_wrapper=None, + defines : T.Optional[T.List[str]] = None, **kwargs): CPPCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self) + ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'0': [], '1': default_warn_args, @@ -238,7 +239,9 @@ class EmscriptenCPPCompiler(EmscriptenMixin, LinkerEnvVarsMixin, ClangCPPCompile class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): def __init__(self, exelist, version, for_machine: MachineChoice, is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): - CPPCompiler.__init__(self, exelist, version, for_machine, is_cross, exe_wrapper, **kwargs) + CPPCompiler.__init__(self, exelist=exelist, version=version, + for_machine=for_machine, is_cross=is_cross, + info=info, exe_wrapper=exe_wrapper, **kwargs) ArmclangCompiler.__init__(self) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'0': [], diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index e839f53..4e89f5d 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -271,9 +271,10 @@ class CudaCompiler(Compiler): def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: - return self._cook_link_args(self.host_compiler.build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath)) + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + (rpath_args, rpath_dirs_to_remove) = self.host_compiler.build_rpath_args( + env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + return (self._cook_link_args(rpath_args), rpath_dirs_to_remove) def linker_to_compiler_args(self, args): return args diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index a83e221..777fa19 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -220,7 +220,7 @@ class DmdLikeCompilerMixin: def build_rpath_args(self, env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): if self.info.is_windows(): - return [] + return ([], set()) # GNU ld, solaris ld, and lld acting like GNU ld if self.linker.id.startswith('ld'): @@ -228,15 +228,16 @@ class DmdLikeCompilerMixin: # do directly, each argument -rpath and the value to rpath, need to be # split into two separate arguments both prefaced with the -L=. args = [] - for r in super().build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): + (rpath_args, rpath_dirs_to_remove) = super().build_rpath_args( + env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + for r in rpath_args: if ',' in r: a, b = r.split(',', maxsplit=1) args.append(a) args.append(self.LINKER_PREFIX + b) else: args.append(r) - return args + return (args, rpath_dirs_to_remove) return super().build_rpath_args( env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) @@ -645,7 +646,8 @@ class GnuDCompiler(GnuCompiler, DCompiler): '1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - self.base_options = ['b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt', 'b_coverage'] + self.base_options = ['b_colorout', 'b_sanitize', 'b_staticpic', + 'b_vscrt', 'b_coverage', 'b_pgo', 'b_ndebug'] self._has_color_support = version_compare(self.version, '>=4.9') # dependencies were implemented before, but broken - support was fixed in GCC 7.1+ @@ -684,6 +686,9 @@ class GnuDCompiler(GnuCompiler, DCompiler): return args return args + ['-shared-libphobos'] + def get_disable_assert_args(self): + return ['-frelease'] + class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): @@ -691,7 +696,7 @@ class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): info: 'MachineInfo', arch, **kwargs): DCompiler.__init__(self, exelist, version, for_machine, info, arch, False, None, **kwargs) self.id = 'llvm' - self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt'] + self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug'] def get_colorout_args(self, colortype): if colortype == 'always': @@ -733,6 +738,9 @@ class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): return args return args + ['-link-defaultlib-shared'] + def get_disable_assert_args(self) -> T.List[str]: + return ['--release'] + class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): @@ -740,7 +748,7 @@ class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): info: 'MachineInfo', arch, **kwargs): DCompiler.__init__(self, exelist, version, for_machine, info, arch, False, None, **kwargs) self.id = 'dmd' - self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt'] + self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug'] def get_colorout_args(self, colortype): if colortype == 'always': @@ -803,3 +811,6 @@ class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): if self.info.is_windows(): return args return args + ['-defaultlib=phobos2', '-debuglib=phobos2'] + + def get_disable_assert_args(self) -> T.List[str]: + return ['-release'] diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 01283a1..af83c0e56 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -214,6 +214,18 @@ class GnuFortranCompiler(GnuCompiler, FortranCompiler): def language_stdlib_only_link_flags(self) -> T.List[str]: return ['-lgfortran', '-lm'] + def has_header(self, hname, prefix, env, *, extra_args=None, dependencies=None, disable_cache=False): + ''' + Derived from mixins/clike.py:has_header, but without C-style usage of + __has_include which breaks with GCC-Fortran 10: + https://github.com/mesonbuild/meson/issues/7017 + ''' + fargs = {'prefix': prefix, 'header': hname} + code = '{prefix}\n#include <{header}>' + return self.compiles(code.format(**fargs), env, extra_args=extra_args, + dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) + + class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler): def __init__(self, exelist, version, for_machine: MachineChoice, is_cross, info: 'MachineInfo', exe_wrapper=None, @@ -412,7 +424,7 @@ class FlangFortranCompiler(ClangCompiler, FortranCompiler): **kwargs): FortranCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self) + ClangCompiler.__init__(self, []) self.id = 'flang' default_warn_args = ['-Minform=inform'] self.warn_args = {'0': [], diff --git a/mesonbuild/compilers/mixins/arm.py b/mesonbuild/compilers/mixins/arm.py index aa5d15d..b331d8f 100644 --- a/mesonbuild/compilers/mixins/arm.py +++ b/mesonbuild/compilers/mixins/arm.py @@ -27,10 +27,10 @@ if T.TYPE_CHECKING: arm_buildtype_args = { 'plain': [], - 'debug': ['-O0', '--debug'], - 'debugoptimized': ['-O1', '--debug'], - 'release': ['-O3', '-Otime'], - 'minsize': ['-O3', '-Ospace'], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], 'custom': [], } # type: T.Dict[str, T.List[str]] @@ -38,27 +38,27 @@ arm_optimization_args = { '0': ['-O0'], 'g': ['-g'], '1': ['-O1'], - '2': ['-O2'], - '3': ['-O3'], - 's': [], + '2': [], # Compiler defaults to -O2 + '3': ['-O3', '-Otime'], + 's': ['-O3'], # Compiler defaults to -Ospace } # type: T.Dict[str, T.List[str]] armclang_buildtype_args = { 'plain': [], - 'debug': ['-O0', '-g'], - 'debugoptimized': ['-O1', '-g'], - 'release': ['-Os'], - 'minsize': ['-Oz'], + 'debug': [], + 'debugoptimized': [], + 'release': [], + 'minsize': [], 'custom': [], } # type: T.Dict[str, T.List[str]] armclang_optimization_args = { - '0': ['-O0'], + '0': [], # Compiler defaults to -O0 'g': ['-g'], '1': ['-O1'], '2': ['-O2'], '3': ['-O3'], - 's': ['-Os'] + 's': ['-Oz'] } # type: T.Dict[str, T.List[str]] @@ -181,7 +181,7 @@ class ArmclangCompiler: # Override CCompiler.get_dependency_gen_args def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: - return [] + return ['-MD', '-MT', outtarget, '-MF', outfile] def get_optimization_args(self, optimization_level: str) -> T.List[str]: return armclang_optimization_args[optimization_level] diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index 1c0ee45..ecfbc64 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -42,9 +42,10 @@ clang_optimization_args = { } # type: T.Dict[str, T.List[str]] class ClangCompiler(GnuLikeCompiler): - def __init__(self): + def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.id = 'clang' + self.defines = defines or {} self.base_options.append('b_colorout') # TODO: this really should be part of the linker base_options, but # linkers don't have base_options. @@ -56,6 +57,12 @@ class ClangCompiler(GnuLikeCompiler): def get_colorout_args(self, colortype: str) -> T.List[str]: return clang_color_args[colortype][:] + def has_builtin_define(self, define: str) -> bool: + return define in self.defines + + def get_builtin_define(self, define: str) -> T.Optional[str]: + return self.defines.get(define) + def get_optimization_args(self, optimization_level: str) -> T.List[str]: return clang_optimization_args[optimization_level] @@ -117,3 +124,6 @@ class ClangCompiler(GnuLikeCompiler): # Clang only warns about unknown or ignored attributes, so force an # error. return ['-Werror=attributes'] + + def get_coverage_link_args(self) -> T.List[str]: + return ['--coverage'] diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index 124c49c..0ed0baa 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -254,14 +254,14 @@ class CLikeCompiler: code = 'int main(void) { int class=0; return class; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) - def check_header(self, hname, prefix, env, *, extra_args=None, dependencies=None): + def check_header(self, hname: str, prefix: str, env, *, extra_args=None, dependencies=None): fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #include <{header}>''' return self.compiles(code.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def has_header(self, hname, prefix, env, *, extra_args=None, dependencies=None, disable_cache=False): + def has_header(self, hname: str, prefix: str, env, *, extra_args=None, dependencies=None, disable_cache: bool = False): fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #ifdef __has_include @@ -274,7 +274,7 @@ class CLikeCompiler: return self.compiles(code.format(**fargs), env, extra_args=extra_args, dependencies=dependencies, mode='preprocess', disable_cache=disable_cache) - def has_header_symbol(self, hname, symbol, prefix, env, *, extra_args=None, dependencies=None): + def has_header_symbol(self, hname: str, symbol: str, prefix: str, env, *, extra_args=None, dependencies=None): fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> @@ -288,7 +288,7 @@ class CLikeCompiler: return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def _get_basic_compiler_args(self, env, mode): + def _get_basic_compiler_args(self, env, mode: str): cargs, largs = [], [] # Select a CRT if needed since we're linking if mode == 'link': @@ -354,11 +354,11 @@ class CLikeCompiler: def compiles(self, code: str, env, *, extra_args: T.Sequence[T.Union[T.Sequence[str], str]] = None, - dependencies=None, mode: str = 'compile', disable_cache=False) -> T.Tuple[bool, bool]: + dependencies=None, mode: str = 'compile', disable_cache: bool = False) -> T.Tuple[bool, bool]: with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p: return p.returncode == 0, p.cached - def _build_wrapper(self, code: str, env, extra_args, dependencies=None, mode: str = 'compile', want_output: bool = False, disable_cache: bool = False, temp_dir=None) -> T.Tuple[bool, bool]: + def _build_wrapper(self, code: str, env, extra_args, dependencies=None, mode: str = 'compile', want_output: bool = False, disable_cache: bool = False, temp_dir: str = None) -> T.Tuple[bool, bool]: args = self._get_compiler_check_args(env, extra_args, dependencies, mode) if disable_cache or want_output: return self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=env.scratch_dir) @@ -369,7 +369,8 @@ class CLikeCompiler: dependencies=dependencies, mode='link', disable_cache=disable_cache) def run(self, code: str, env, *, extra_args=None, dependencies=None): - if self.is_cross and self.exe_wrapper is None: + need_exe_wrapper = env.need_exe_wrapper(self.for_machine) + if need_exe_wrapper and self.exe_wrapper is None: raise compilers.CrossNoRunException('Can not run test applications in this cross environment.') with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p: if p.returncode != 0: @@ -377,7 +378,7 @@ class CLikeCompiler: p.input_name, p.returncode)) return compilers.RunResult(False) - if self.is_cross: + if need_exe_wrapper: cmdlist = self.exe_wrapper + [p.output_name] else: cmdlist = p.output_name @@ -658,7 +659,7 @@ class CLikeCompiler: # is not run so we don't care what the return value is. main = '''\nint main(void) {{ void *a = (void*) &{func}; - long b = (long) a; + long long b = (long long) a; return (int) b; }}''' return head, main @@ -727,24 +728,29 @@ class CLikeCompiler: # need to look for them differently. On nice compilers like clang, we # can just directly use the __has_builtin() macro. fargs['no_includes'] = '#include' not in prefix - fargs['__builtin_'] = '' if funcname.startswith('__builtin_') else '__builtin_' + is_builtin = funcname.startswith('__builtin_') + fargs['is_builtin'] = is_builtin + fargs['__builtin_'] = '' if is_builtin else '__builtin_' t = '''{prefix} int main(void) {{ + + /* With some toolchains (MSYS2/mingw for example) the compiler + * provides various builtins which are not really implemented and + * fall back to the stdlib where they aren't provided and fail at + * build/link time. In case the user provides a header, including + * the header didn't lead to the function being defined, and the + * function we are checking isn't a builtin itself we assume the + * builtin is not functional and we just error out. */ + #if !{no_includes:d} && !defined({func}) && !{is_builtin:d} + #error "No definition for {__builtin_}{func} found in the prefix" + #endif + #ifdef __has_builtin #if !__has_builtin({__builtin_}{func}) #error "{__builtin_}{func} not found" #endif #elif ! defined({func}) - /* Check for {__builtin_}{func} only if no includes were added to the - * prefix above, which means no definition of {func} can be found. - * We would always check for this, but we get false positives on - * MSYS2 if we do. Their toolchain is broken, but we can at least - * give them a workaround. */ - #if {no_includes:d} - {__builtin_}{func}; - #else - #error "No definition for {__builtin_}{func} found in the prefix" - #endif + {__builtin_}{func}; #endif return 0; }}''' @@ -910,21 +916,21 @@ class CLikeCompiler: architecture. ''' # If not building on macOS for Darwin, do a simple file check - files = [Path(f) for f in files] + paths = [Path(f) for f in files] if not env.machines.host.is_darwin() or not env.machines.build.is_darwin(): - for f in files: - if f.is_file(): - return f + for p in paths: + if p.is_file(): + return p # Run `lipo` and check if the library supports the arch we want - for f in files: - if not f.is_file(): + for p in paths: + if not p.is_file(): continue - archs = mesonlib.darwin_get_object_archs(str(f)) + archs = mesonlib.darwin_get_object_archs(str(p)) if archs and env.machines.host.cpu_family in archs: - return f + return p else: mlog.debug('Rejected {}, supports {} but need {}' - .format(f, archs, env.machines.host.cpu_family)) + .format(p, archs, env.machines.host.cpu_family)) return None @functools.lru_cache() @@ -1131,3 +1137,6 @@ class CLikeCompiler: return self.compiles(self.attribute_check_func(name), env, extra_args=self.get_has_func_attribute_extra_args(name)) + + def get_disable_assert_args(self) -> T.List[str]: + return ['-DNDEBUG'] diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py index 681c816..bf1d339 100644 --- a/mesonbuild/compilers/mixins/islinker.py +++ b/mesonbuild/compilers/mixins/islinker.py @@ -107,8 +107,8 @@ class BasicLinkerIsCompilerMixin: def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: - return [] + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) def get_linker_debug_crt_args(self) -> T.List[str]: return [] diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 52d258d..d351c88 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -86,7 +86,7 @@ class ClangObjCCompiler(ClangCompiler, ObjCCompiler): **kwargs): ObjCCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self) + ClangCompiler.__init__(self, []) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], '1': default_warn_args, diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index c8b422b..10555b4 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -84,7 +84,7 @@ class ClangObjCPPCompiler(ClangCompiler, ObjCPPCompiler): is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs): ObjCPPCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, **kwargs) - ClangCompiler.__init__(self) + ClangCompiler.__init__(self, []) default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'] self.warn_args = {'0': [], '1': default_warn_args, diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 0b79084..8774b80 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -99,16 +99,16 @@ class UserBooleanOption(UserOption[bool]): class UserIntegerOption(UserOption[int]): def __init__(self, description, value, yielding=None): min_value, max_value, default_value = value - super().__init__(description, [True, False], yielding) self.min_value = min_value self.max_value = max_value - self.set_value(default_value) c = [] if min_value is not None: c.append('>=' + str(min_value)) if max_value is not None: c.append('<=' + str(max_value)) - self.choices = ', '.join(c) + choices = ', '.join(c) + super().__init__(description, choices, yielding) + self.set_value(default_value) def validate_value(self, value) -> int: if isinstance(value, str): @@ -384,6 +384,7 @@ class CoreData: self.compiler_check_cache = OrderedDict() # Only to print a warning if it changes between Meson invocations. self.config_files = self.__load_config_files(options, scratch_dir, 'native') + self.builtin_options_libdir_cross_fixup() self.init_builtins('') @staticmethod @@ -444,12 +445,12 @@ class CoreData: raise MesonException('Cannot find specified {} file: {}'.format(ftype, f)) return real - def libdir_cross_fixup(self): + def builtin_options_libdir_cross_fixup(self): # By default set libdir to "lib" when cross compiling since # getting the "system default" is always wrong on multiarch # platforms as it gets a value like lib/x86_64-linux-gnu. if self.cross_files: - self.builtins['libdir'].value = 'lib' + builtin_options['libdir'].default = 'lib' def sanitize_prefix(self, prefix): prefix = os.path.expanduser(prefix) @@ -490,7 +491,7 @@ class CoreData: # commonpath will always return a path in the native format, so we # must use pathlib.PurePath to do the same conversion before # comparing. - msg = ('The value of the {!r} option is {!r} which must be a ' + msg = ('The value of the {!r} option is \'{!s}\' which must be a ' 'subdir of the prefix {!r}.\nNote that if you pass a ' 'relative path, it is assumed to be a subdir of prefix.') # os.path.commonpath doesn't understand case-insensitive filesystems, @@ -510,7 +511,6 @@ class CoreData: for for_machine in iter(MachineChoice): for key, opt in builtin_options_per_machine.items(): self.add_builtin_option(self.builtins_per_machine[for_machine], key, opt, subproject) - self.libdir_cross_fixup() def add_builtin_option(self, opts_map, key, opt, subproject): if subproject: @@ -682,7 +682,9 @@ class CoreData: if type(oldval) != type(value): self.user_options[name] = value - def is_cross_build(self) -> bool: + def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: + if when_building_for == MachineChoice.BUILD: + return False return len(self.cross_files) > 0 def strip_build_option_names(self, options): diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index c0ec089..b0401c6 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -437,28 +437,11 @@ class ConfigToolDependency(ExternalDependency): """ if not isinstance(versions, list) and versions is not None: versions = listify(versions) - - tool = self.env.lookup_binary_entry(self.for_machine, self.tool_name) - if tool is not None: - tools = [tool] - else: - if not self.env.machines.matches_build_machine(self.for_machine): - mlog.deprecation('No entry for {0} specified in your cross file. ' - 'Falling back to searching PATH. This may find a ' - 'native version of {0}! This will become a hard ' - 'error in a future version of meson'.format(self.tool_name)) - tools = [[t] for t in self.tools] - best_match = (None, None) - for tool in tools: - if len(tool) == 1: - # In some situations the command can't be directly executed. - # For example Shell scripts need to be called through sh on - # Windows (see issue #1423). - potential_bin = ExternalProgram(tool[0], silent=True) - if not potential_bin.found(): - continue - tool = potential_bin.get_command() + for potential_bin in self.search_tool(self.tool_name, self.tool_name, self.tools): + if not potential_bin.found(): + continue + tool = potential_bin.get_command() try: p, out = Popen_safe(tool + [self.version_arg])[:2] except (FileNotFoundError, PermissionError): @@ -1460,8 +1443,15 @@ class CMakeDependency(ExternalDependency): cfgs = [x for x in tgt.properties['IMPORTED_CONFIGURATIONS'] if x] cfg = cfgs[0] - if 'RELEASE' in cfgs: - cfg = 'RELEASE' + is_debug = self.env.coredata.get_builtin_option('debug'); + if is_debug: + if 'DEBUG' in cfgs: + cfg = 'DEBUG' + elif 'RELEASE' in cfgs: + cfg = 'RELEASE' + else: + if 'RELEASE' in cfgs: + cfg = 'RELEASE' if 'IMPORTED_IMPLIB_{}'.format(cfg) in tgt.properties: libraries += [x for x in tgt.properties['IMPORTED_IMPLIB_{}'.format(cfg)] if x] @@ -1800,6 +1790,10 @@ class ExternalProgram: self.name = name if command is not None: self.command = listify(command) + if mesonlib.is_windows(): + cmd = self.command[0] + args = self.command[1:] + self.command = self._search_windows_special_cases(name, cmd) + args else: all_search_dirs = [search_dir] if extra_search_dirs: diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 13054f5..907c0c2 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -22,7 +22,7 @@ from .. import mlog from .. import mesonlib from ..environment import Environment -from .base import (DependencyException, ExternalDependency) +from .base import DependencyException, ExternalDependency, PkgConfigDependency from .misc import threads_factory # On windows 3 directory layouts are supported: @@ -189,13 +189,13 @@ class BoostLibraryFile(): def __lt__(self, other: T.Any) -> bool: if isinstance(other, BoostLibraryFile): return ( - self.mod_name, self.version_lib, self.arch, self.static, + self.mod_name, self.static, self.version_lib, self.arch, not self.mt, not self.runtime_static, not self.debug, self.runtime_debug, self.python_debug, self.stlport, self.deprecated_iostreams, self.name, ) < ( - other.mod_name, other.version_lib, other.arch, other.static, + other.mod_name, other.static, other.version_lib, other.arch, not other.mt, not other.runtime_static, not other.debug, other.runtime_debug, other.python_debug, other.stlport, other.deprecated_iostreams, @@ -344,6 +344,7 @@ class BoostDependency(ExternalDependency): self.multithreading = kwargs.get('threading', 'multi') == 'multi' self.boost_root = None + self.explicit_static = 'static' in kwargs # Extract and validate modules self.modules = mesonlib.extract_as_list(kwargs, 'modules') # type: T.List[str] @@ -411,10 +412,19 @@ class BoostDependency(ExternalDependency): break def run_check(self, inc_dirs: T.List[BoostIncludeDir], lib_dirs: T.List[Path]) -> bool: + mlog.debug(' - potential library dirs: {}'.format([x.as_posix() for x in lib_dirs])) + mlog.debug(' - potential include dirs: {}'.format([x.path.as_posix() for x in inc_dirs])) + # 2. Find all boost libraries libs = [] # type: T.List[BoostLibraryFile] for i in lib_dirs: - libs += self.detect_libraries(i) + libs = self.detect_libraries(i) + if libs: + mlog.debug(' - found boost library dir: {}'.format(i)) + # mlog.debug(' - raw library list:') + # for j in libs: + # mlog.debug(' - {}'.format(j)) + break libs = sorted(set(libs)) modules = ['boost_' + x for x in self.modules] @@ -422,9 +432,6 @@ class BoostDependency(ExternalDependency): mlog.debug(' - found boost {} include dir: {}'.format(inc.version, inc.path)) f_libs = self.filter_libraries(libs, inc.version_lib) - # mlog.debug(' - raw library list:') - # for j in libs: - # mlog.debug(' - {}'.format(j)) mlog.debug(' - filtered library list:') for j in f_libs: mlog.debug(' - {}'.format(j)) @@ -499,6 +506,19 @@ class BoostDependency(ExternalDependency): return [self._include_dir_from_version_header(x) for x in candidates] def detect_lib_dirs(self, root: Path) -> T.List[Path]: + # First check the system include paths. Only consider those within the + # given root path + system_dirs_t = self.clib_compiler.get_library_dirs(self.env) + system_dirs = [Path(x) for x in system_dirs_t] + system_dirs = [x.resolve() for x in system_dirs if x.exists()] + system_dirs = [x for x in system_dirs if mesonlib.path_is_in_root(x, root)] + system_dirs = list(mesonlib.OrderedSet(system_dirs)) + + if system_dirs: + return system_dirs + + # No system include paths were found --> fall back to manually looking + # for library dirs in root dirs = [] # type: T.List[Path] subdirs = [] # type: T.List[Path] for i in root.iterdir(): @@ -510,7 +530,25 @@ class BoostDependency(ExternalDependency): for j in i.iterdir(): if j.is_dir() and j.name.endswith('-linux-gnu'): subdirs += [j] - return dirs + subdirs + + # Filter out paths that don't match the target arch to avoid finding + # the wrong libraries. See https://github.com/mesonbuild/meson/issues/7110 + if not self.arch: + return dirs + subdirs + + arch_list_32 = ['32', 'i386'] + arch_list_64 = ['64'] + + raw_list = dirs + subdirs + no_arch = [x for x in raw_list if not any([y in x.name for y in arch_list_32 + arch_list_64])] + + matching_arch = [] # type: T.List[Path] + if '32' in self.arch: + matching_arch = [x for x in raw_list if any([y in x.name for y in arch_list_32])] + elif '64' in self.arch: + matching_arch = [x for x in raw_list if any([y in x.name for y in arch_list_64])] + + return sorted(matching_arch) + sorted(no_arch) def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.List[BoostLibraryFile]: # MSVC is very picky with the library tags @@ -522,7 +560,7 @@ class BoostDependency(ExternalDependency): except (KeyError, IndexError, AttributeError): pass - libs = [x for x in libs if x.static == self.static] + libs = [x for x in libs if x.static == self.static or not self.explicit_static] libs = [x for x in libs if x.mt == self.multithreading] libs = [x for x in libs if x.version_matches(lib_vers)] libs = [x for x in libs if x.arch_matches(self.arch)] @@ -567,6 +605,23 @@ class BoostDependency(ExternalDependency): roots += paths return roots # Do not add system paths if BOOST_ROOT is present + # Try getting the BOOST_ROOT from a boost.pc if it exists. This primarely + # allows BoostDependency to find boost from Conan. See #5438 + try: + boost_pc = PkgConfigDependency('boost', self.env, {'required': False}) + if boost_pc.found(): + boost_root = boost_pc.get_pkgconfig_variable('prefix', {'default': None}) + if boost_root: + roots += [Path(boost_root)] + except DependencyException: + pass + + # Add roots from system paths + inc_paths = [Path(x) for x in self.clib_compiler.get_default_include_dirs()] + inc_paths = [x.parent for x in inc_paths if x.exists()] + inc_paths = [x.resolve() for x in inc_paths] + roots += inc_paths + # Add system paths if self.env.machines[self.for_machine].is_windows(): # Where boost built from source actually installs it @@ -588,8 +643,6 @@ class BoostDependency(ExternalDependency): roots += [x for x in candidates if x.name.lower().startswith('boost') and x.is_dir()] else: tmp = [] # type: T.List[Path] - # Add unix paths - tmp += [Path(x).parent for x in self.clib_compiler.get_default_include_dirs()] # Homebrew brew_boost = Path('/usr/local/Cellar/boost') @@ -637,11 +690,8 @@ class BoostDependency(ExternalDependency): return BoostIncludeDir(hfile.parents[1], int(m.group(1))) def _extra_compile_args(self) -> T.List[str]: - args = [] # type: T.List[str] - args += ['-DBOOST_ALL_NO_LIB'] # Disable automatic linking - if not self.static: - args += ['-DBOOST_ALL_DYN_LINK'] - return args + # BOOST_ALL_DYN_LINK should not be required with the known defines below + return ['-DBOOST_ALL_NO_LIB'] # Disable automatic linking # See https://www.boost.org/doc/libs/1_72_0/more/getting_started/unix-variants.html#library-naming @@ -665,9 +715,9 @@ boost_arch_map = { #### ---- BEGIN GENERATED ---- #### # # # Generated with tools/boost_names.py: -# - boost version: 1.72.0 -# - modules found: 158 -# - libraries found: 42 +# - boost version: 1.73.0 +# - modules found: 159 +# - libraries found: 43 # class BoostLibrary(): @@ -690,16 +740,16 @@ class BoostModule(): boost_libraries = { 'boost_atomic': BoostLibrary( name='boost_atomic', - shared=[], - static=[], + shared=['-DBOOST_ATOMIC_DYN_LINK=1'], + static=['-DBOOST_ATOMIC_STATIC_LINK=1'], single=[], multi=[], ), 'boost_chrono': BoostLibrary( name='boost_chrono', - shared=['-DBOOST_ALL_DYN_LINK=1'], - static=['-DBOOST_All_STATIC_LINK=1'], - single=[], + shared=['-DBOOST_CHRONO_DYN_LINK=1'], + static=['-DBOOST_CHRONO_STATIC_LINK=1'], + single=['-DBOOST_CHRONO_THREAD_DISABLED'], multi=[], ), 'boost_container': BoostLibrary( @@ -711,28 +761,28 @@ boost_libraries = { ), 'boost_context': BoostLibrary( name='boost_context', - shared=[], + shared=['-DBOOST_CONTEXT_DYN_LINK=1'], static=[], single=[], multi=[], ), 'boost_contract': BoostLibrary( name='boost_contract', - shared=[], - static=[], - single=[], + shared=['-DBOOST_CONTRACT_DYN_LINK'], + static=['-DBOOST_CONTRACT_STATIC_LINK'], + single=['-DBOOST_CONTRACT_DISABLE_THREADS'], multi=[], ), 'boost_coroutine': BoostLibrary( name='boost_coroutine', - shared=[], + shared=['-DBOOST_COROUTINES_DYN_LINK=1'], static=[], single=[], multi=[], ), 'boost_date_time': BoostLibrary( name='boost_date_time', - shared=[], + shared=['-DBOOST_DATE_TIME_DYN_LINK=1'], static=[], single=[], multi=[], @@ -746,14 +796,14 @@ boost_libraries = { ), 'boost_fiber': BoostLibrary( name='boost_fiber', - shared=[], + shared=['-DBOOST_FIBERS_DYN_LINK=1'], static=[], single=[], multi=[], ), 'boost_fiber_numa': BoostLibrary( name='boost_fiber_numa', - shared=[], + shared=['-DBOOST_FIBERS_DYN_LINK=1'], static=[], single=[], multi=[], @@ -767,84 +817,91 @@ boost_libraries = { ), 'boost_graph': BoostLibrary( name='boost_graph', - shared=['-DBOOST_GRAPH_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_iostreams': BoostLibrary( name='boost_iostreams', - shared=['-DBOOST_IOSTREAMS_DYN_LINK=1', '-DBOOST_IOSTREAMS_DYN_LINK=1'], + shared=['-DBOOST_IOSTREAMS_DYN_LINK=1'], static=[], single=[], multi=[], ), 'boost_locale': BoostLibrary( name='boost_locale', - shared=['-DBOOST_LOCALE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_log': BoostLibrary( name='boost_log', - shared=['-DBOOST_LOG_DLL', '-DBOOST_LOG_DYN_LINK=1'], + shared=['-DBOOST_LOG_DYN_LINK=1'], static=[], - single=['BOOST_LOG_NO_THREADS'], + single=['-DBOOST_LOG_NO_THREADS'], multi=[], ), 'boost_log_setup': BoostLibrary( name='boost_log_setup', - shared=['-DBOOST_LOG_DYN_LINK=1', '-DBOOST_LOG_SETUP_DLL', '-DBOOST_LOG_SETUP_DYN_LINK=1'], + shared=['-DBOOST_LOG_SETUP_DYN_LINK=1'], static=[], - single=['BOOST_LOG_NO_THREADS'], + single=['-DBOOST_LOG_NO_THREADS'], multi=[], ), 'boost_math_c99': BoostLibrary( name='boost_math_c99', - shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_math_c99f': BoostLibrary( name='boost_math_c99f', - shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_math_c99l': BoostLibrary( name='boost_math_c99l', - shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_math_tr1': BoostLibrary( name='boost_math_tr1', - shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_math_tr1f': BoostLibrary( name='boost_math_tr1f', - shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_math_tr1l': BoostLibrary( name='boost_math_tr1l', - shared=['-DBOOST_MATH_TR1_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_mpi': BoostLibrary( name='boost_mpi', - shared=['-DBOOST_MPI_DYN_LINK=1'], + shared=[], + static=[], + single=[], + multi=[], + ), + 'boost_nowide': BoostLibrary( + name='boost_nowide', + shared=['-DBOOST_NOWIDE_DYN_LINK=1'], static=[], single=[], multi=[], @@ -865,63 +922,63 @@ boost_libraries = { ), 'boost_random': BoostLibrary( name='boost_random', - shared=[], + shared=['-DBOOST_RANDOM_DYN_LINK'], static=[], single=[], multi=[], ), 'boost_regex': BoostLibrary( name='boost_regex', - shared=['-DBOOST_REGEX_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_serialization': BoostLibrary( name='boost_serialization', - shared=['-DBOOST_SERIALIZATION_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_stacktrace_addr2line': BoostLibrary( name='boost_stacktrace_addr2line', - shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_stacktrace_backtrace': BoostLibrary( name='boost_stacktrace_backtrace', - shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_stacktrace_basic': BoostLibrary( name='boost_stacktrace_basic', - shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_stacktrace_noop': BoostLibrary( name='boost_stacktrace_noop', - shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_stacktrace_windbg': BoostLibrary( name='boost_stacktrace_windbg', - shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], ), 'boost_stacktrace_windbg_cached': BoostLibrary( name='boost_stacktrace_windbg_cached', - shared=['-DBOOST_STACKTRACE_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], @@ -942,8 +999,8 @@ boost_libraries = { ), 'boost_thread': BoostLibrary( name='boost_thread', - shared=['-DBOOST_THREAD_USE_DLL=1'], - static=['-DBOOST_THREAD_USE_LIB=1'], + shared=['-DBOOST_THREAD_BUILD_DLL=1', '-DBOOST_THREAD_USE_DLL=1'], + static=['-DBOOST_THREAD_BUILD_LIB=1', '-DBOOST_THREAD_USE_LIB=1'], single=[], multi=[], ), @@ -956,7 +1013,7 @@ boost_libraries = { ), 'boost_type_erasure': BoostLibrary( name='boost_type_erasure', - shared=[], + shared=['-DBOOST_TYPE_ERASURE_DYN_LINK'], static=[], single=[], multi=[], @@ -977,7 +1034,7 @@ boost_libraries = { ), 'boost_wserialization': BoostLibrary( name='boost_wserialization', - shared=['-DBOOST_SERIALIZATION_DYN_LINK=1'], + shared=[], static=[], single=[], multi=[], diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 4cec814..741f0b8 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -406,6 +406,9 @@ class QtBaseDependency(ExternalDependency): if libfile: libfile = libfile[0] else: + mlog.log("Could not find:", module, + self.qtpkgname + module + modules_lib_suffix, + 'in', libdir) self.is_found = False break self.link_args.append(libfile) @@ -426,6 +429,20 @@ class QtBaseDependency(ExternalDependency): if self.env.machines[self.for_machine].is_darwin(): if is_debug: suffix += '_debug' + if mesonlib.version_compare(self.version, '>= 5.14.0'): + if self.env.machines[self.for_machine].is_android(): + cpu_family = self.env.machines[self.for_machine].cpu_family + if cpu_family == 'x86': + suffix += '_x86' + elif cpu_family == 'x86_64': + suffix += '_x86_64' + elif cpu_family == 'arm': + suffix += '_armeabi-v7a' + elif cpu_family == 'aarch64': + suffix += '_arm64-v8a' + else: + mlog.warning('Android target arch {!r} for Qt5 is unknown, ' + 'module detection may not work'.format(cpu_family)) return suffix def _link_with_qtmain(self, is_debug, libdir): diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 25b3c7f..b74be35 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -40,6 +40,7 @@ known_cpu_families = ( 'alpha', 'arc', 'arm', + 'avr', 'c2000', 'e2k', 'ia64', @@ -121,7 +122,7 @@ def get_env_var_pair(for_machine: MachineChoice, # ones. ([var_name + '_FOR_BUILD'] if is_cross else [var_name]), # Always just the unprefixed host verions - ([] if is_cross else [var_name]), + [var_name] )[for_machine] for var in candidates: value = os.environ.get(var) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 64efda6..cb6ae7d 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -635,8 +635,8 @@ class Environment: self.coredata.meson_command = mesonlib.meson_command self.first_invocation = True - def is_cross_build(self) -> bool: - return self.coredata.is_cross_build() + def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: + return self.coredata.is_cross_build(when_building_for) def dump_coredata(self): return coredata.save(self.coredata, self.get_build_dir()) @@ -726,6 +726,28 @@ class Environment: minor = defines.get('__LCC_MINOR__', '0') return dot.join((generation, major, minor)) + @staticmethod + def get_clang_compiler_defines(compiler): + """ + Get the list of Clang pre-processor defines + """ + args = compiler + ['-E', '-dM', '-'] + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to get clang pre-processor defines:\n' + output + error) + defines = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest] = True + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + def _get_compilers(self, lang, for_machine): ''' The list of compilers is detected in the exact same way for @@ -899,7 +921,7 @@ class Environment: def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice) -> Compiler: popen_exceptions = {} compilers, ccache, exe_wrap = self._get_compilers(lang, for_machine) - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] for compiler in compilers: @@ -985,12 +1007,15 @@ class Environment: if 'Emscripten' in out: cls = EmscriptenCCompiler if lang == 'c' else EmscriptenCPPCompiler self.coredata.add_lang_args(cls.language, cls, for_machine, self) - # emcc cannot be queried to get the version out of it (it - # ignores -Wl,--version and doesn't have an alternative). - # Further, wasm-id *is* lld and will return `LLD X.Y.Z` if you - # call `wasm-ld --version`, but a special version of lld that - # takes different options. - p, o, _ = Popen_safe(['wasm-ld', '--version']) + + # emcc requires a file input in order to pass arguments to the + # linker. It'll exit with an error code, but still print the + # linker version. Old emcc versions ignore -Wl,--version completely, + # however. We'll report "unknown version" in that case. + with tempfile.NamedTemporaryFile(suffix='.c') as f: + cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name] + _, o, _ = Popen_safe(cmd) + linker = WASMDynamicLinker( compiler, for_machine, cls.LINKER_PREFIX, [], version=search_version(o)) @@ -1040,6 +1065,8 @@ class Environment: if 'clang' in out: linker = None + defines = self.get_clang_compiler_defines(compiler) + # Even if the for_machine is darwin, we could be using vanilla # clang. if 'Apple' in out: @@ -1060,7 +1087,7 @@ class Environment: return cls( ccache + compiler, version, for_machine, is_cross, info, - exe_wrap, full_version=full_version, linker=linker) + exe_wrap, defines, full_version=full_version, linker=linker) if 'Intel(R) C++ Intel(R)' in err: version = search_version(err) @@ -1149,7 +1176,7 @@ class Environment: def detect_cuda_compiler(self, for_machine): popen_exceptions = {} - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) compilers, ccache, exe_wrap = self._get_compilers('cuda', for_machine) info = self.machines[for_machine] for compiler in compilers: @@ -1189,7 +1216,7 @@ class Environment: def detect_fortran_compiler(self, for_machine: MachineChoice): popen_exceptions = {} compilers, ccache, exe_wrap = self._get_compilers('fortran', for_machine) - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] for compiler in compilers: if isinstance(compiler, str): @@ -1308,7 +1335,7 @@ class Environment: def _detect_objc_or_objcpp_compiler(self, for_machine: MachineInfo, objc: bool) -> 'Compiler': popen_exceptions = {} compilers, ccache, exe_wrap = self._get_compilers('objc' if objc else 'objcpp', for_machine) - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] for compiler in compilers: @@ -1399,7 +1426,7 @@ class Environment: def detect_vala_compiler(self, for_machine): exelist = self.lookup_binary_entry(for_machine, 'vala') - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] if exelist is None: # TODO support fallback @@ -1419,7 +1446,7 @@ class Environment: def detect_rust_compiler(self, for_machine): popen_exceptions = {} compilers, ccache, exe_wrap = self._get_compilers('rust', for_machine) - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] cc = self.detect_c_compiler(for_machine) @@ -1510,7 +1537,7 @@ class Environment: arch = 'x86_mscoff' popen_exceptions = {} - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) results, ccache, exe_wrap = self._get_compilers('d', for_machine) for exelist in results: # Search for a D compiler. @@ -1601,7 +1628,7 @@ class Environment: def detect_swift_compiler(self, for_machine): exelist = self.lookup_binary_entry(for_machine, 'swift') - is_cross = not self.machines.matches_build_machine(for_machine) + is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] if exelist is None: # TODO support fallback diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 0303e6a..b8d4fec 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -29,10 +29,11 @@ from .interpreterbase import InterpreterBase from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound -from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs +from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs from .interpreterbase import ObjectHolder from .modules import ModuleReturnValue from .cmake import CMakeInterpreter +from .backend.backends import TestProtocol from pathlib import Path, PurePath import os @@ -509,11 +510,14 @@ class DependencyHolder(InterpreterObject, ObjectHolder): return DependencyHolder(new_dep, self.subproject) class ExternalProgramHolder(InterpreterObject, ObjectHolder): - def __init__(self, ep): + def __init__(self, ep, subproject, backend=None): InterpreterObject.__init__(self) ObjectHolder.__init__(self, ep) + self.subproject = subproject + self.backend = backend self.methods.update({'found': self.found_method, - 'path': self.path_method}) + 'path': self.path_method, + 'full_path': self.full_path_method}) self.cached_version = None @noPosargs @@ -523,8 +527,22 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): @noPosargs @permittedKwargs({}) + @FeatureDeprecated('ExternalProgram.path', '0.55.0', + 'use ExternalProgram.full_path() instead') def path_method(self, args, kwargs): - return self.held_object.get_path() + return self._full_path() + + @noPosargs + @permittedKwargs({}) + @FeatureNew('ExternalProgram.full_path', '0.55.0') + def full_path_method(self, args, kwargs): + return self._full_path() + + def _full_path(self): + exe = self.held_object + if isinstance(exe, build.Executable): + return self.backend.get_target_filename_abs(exe) + return exe.get_path() def found(self): return isinstance(self.held_object, build.Executable) or self.held_object.found() @@ -533,9 +551,14 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): return self.held_object.get_command() def get_name(self): - return self.held_object.get_name() + exe = self.held_object + if isinstance(exe, build.Executable): + return exe.name + return exe.get_name() def get_version(self, interpreter): + if isinstance(self.held_object, build.Executable): + return self.held_object.project_version if not self.cached_version: raw_cmd = self.get_command() + ['--version'] cmd = [self, '--version'] @@ -958,7 +981,7 @@ class Test(InterpreterObject): self.should_fail = should_fail self.timeout = timeout self.workdir = workdir - self.protocol = protocol + self.protocol = TestProtocol.from_str(protocol) self.priority = priority def get_exe(self): @@ -1780,6 +1803,11 @@ class ModuleHolder(InterpreterObject, ObjectHolder): target_machine=self.interpreter.builtin['target_machine'].held_object, current_node=self.current_node ) + # Many modules do for example self.interpreter.find_program_impl(), + # so we have to ensure they use the current interpreter and not the one + # that first imported that module, otherwise it will use outdated + # overrides. + self.held_object.interpreter = self.interpreter if self.held_object.is_snippet(method_name): value = fn(self.interpreter, state, args, kwargs) return self.interpreter.holderify(value) @@ -1846,6 +1874,7 @@ class MesonMain(InterpreterObject): self.methods.update({'get_compiler': self.get_compiler_method, 'is_cross_build': self.is_cross_build_method, 'has_exe_wrapper': self.has_exe_wrapper_method, + 'can_run_host_binaries': self.can_run_host_binaries_method, 'is_unity': self.is_unity_method, 'is_subproject': self.is_subproject_method, 'current_source_dir': self.current_source_dir_method, @@ -1867,48 +1896,101 @@ class MesonMain(InterpreterObject): 'backend': self.backend_method, }) - def _find_source_script(self, name, args): + def _find_source_script(self, prog: T.Union[str, ExecutableHolder], args): + if isinstance(prog, ExecutableHolder): + prog_path = self.interpreter.backend.get_target_filename(prog.held_object) + return build.RunScript([prog_path], args) + elif isinstance(prog, ExternalProgramHolder): + return build.RunScript(prog.get_command(), args) + # Prefer scripts in the current source directory search_dir = os.path.join(self.interpreter.environment.source_dir, self.interpreter.subdir) - key = (name, search_dir) + key = (prog, search_dir) if key in self._found_source_scripts: found = self._found_source_scripts[key] else: - found = dependencies.ExternalProgram(name, search_dir=search_dir) + found = dependencies.ExternalProgram(prog, search_dir=search_dir) if found.found(): self._found_source_scripts[key] = found else: m = 'Script or command {!r} not found or not executable' - raise InterpreterException(m.format(name)) + raise InterpreterException(m.format(prog)) return build.RunScript(found.get_command(), args) - @permittedKwargs({}) - def add_install_script_method(self, args, kwargs): + def _process_script_args( + self, name: str, args: T.List[T.Union[ + str, mesonlib.File, CustomTargetHolder, + CustomTargetIndexHolder, ConfigureFileHolder, + ExternalProgramHolder, ExecutableHolder, + ]], allow_built: bool = False) -> T.List[str]: + script_args = [] # T.List[str] + new = False + for a in args: + a = unholder(a) + if isinstance(a, str): + script_args.append(a) + elif isinstance(a, mesonlib.File): + new = True + script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir)) + elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): + if not allow_built: + raise InterpreterException('Arguments to {} cannot be built'.format(name)) + new = True + script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()]) + + # This feels really hacky, but I'm not sure how else to fix + # this without completely rewriting install script handling. + # This is complicated by the fact that the install target + # depends on all. + if isinstance(a, build.CustomTargetIndex): + a.target.build_by_default = True + else: + a.build_by_default = True + elif isinstance(a, build.ConfigureFile): + new = True + script_args.append(os.path.join(a.subdir, a.targetname)) + elif isinstance(a, dependencies.ExternalProgram): + script_args.extend(a.command) + new = True + else: + raise InterpreterException( + 'Arguments to {} must be strings, Files, CustomTargets, ' + 'Indexes of CustomTargets, or ConfigureFiles'.format(name)) + if new: + FeatureNew.single_use( + 'Calling "{}" with File, CustomTaget, Index of CustomTarget, ' + 'ConfigureFile, Executable, or ExternalProgram'.format(name), + '0.55.0', self.interpreter.subproject) + return script_args + + @permittedKwargs(set()) + def add_install_script_method(self, args: 'T.Tuple[T.Union[str, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, ConfigureFileHolder], ...]', kwargs): if len(args) < 1: raise InterpreterException('add_install_script takes one or more arguments') - check_stringlist(args, 'add_install_script args must be strings') - script = self._find_source_script(args[0], args[1:]) + script_args = self._process_script_args('add_install_script', args[1:], allow_built=True) + script = self._find_source_script(args[0], script_args) self.build.install_scripts.append(script) - @permittedKwargs({}) + @permittedKwargs(set()) def add_postconf_script_method(self, args, kwargs): if len(args) < 1: raise InterpreterException('add_postconf_script takes one or more arguments') - check_stringlist(args, 'add_postconf_script arguments must be strings') - script = self._find_source_script(args[0], args[1:]) + script_args = self._process_script_args('add_postconf_script', args[1:], allow_built=True) + script = self._find_source_script(args[0], script_args) self.build.postconf_scripts.append(script) - @permittedKwargs({}) + @permittedKwargs(set()) def add_dist_script_method(self, args, kwargs): if len(args) < 1: raise InterpreterException('add_dist_script takes one or more arguments') if len(args) > 1: - FeatureNew('Calling "add_dist_script" with multiple arguments', '0.49.0').use(self.interpreter.subproject) - check_stringlist(args, 'add_dist_script argument must be a string') + FeatureNew.single_use('Calling "add_dist_script" with multiple arguments', + '0.49.0', self.interpreter.subproject) if self.interpreter.subproject != '': raise InterpreterException('add_dist_script may not be used in a subproject.') - script = self._find_source_script(args[0], args[1:]) + script_args = self._process_script_args('add_dist_script', args[1:], allow_built=True) + script = self._find_source_script(args[0], script_args) self.build.dist_scripts.append(script) @noPosargs @@ -1946,9 +2028,16 @@ class MesonMain(InterpreterObject): @noPosargs @permittedKwargs({}) - def has_exe_wrapper_method(self, args, kwargs): - if self.is_cross_build_method(None, None) and \ - self.build.environment.need_exe_wrapper(): + @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.') + def has_exe_wrapper_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: + return self.can_run_host_binaries_method(args, kwargs) + + @noPosargs + @permittedKwargs({}) + @FeatureNew('meson.can_run_host_binaries', '0.55.0') + def can_run_host_binaries_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: + if (self.is_cross_build_method(None, None) and + self.build.environment.need_exe_wrapper()): if self.build.environment.exe_wrapper is None: return False # We return True when exe_wrap is defined, when it's not needed, and @@ -2366,7 +2455,7 @@ class Interpreter(InterpreterBase): elif isinstance(item, dependencies.Dependency): return DependencyHolder(item, self.subproject) elif isinstance(item, dependencies.ExternalProgram): - return ExternalProgramHolder(item) + return ExternalProgramHolder(item, self.subproject) elif hasattr(item, 'held_object'): return item else: @@ -2389,7 +2478,7 @@ class Interpreter(InterpreterBase): elif isinstance(v, build.Data): self.build.data.append(v) elif isinstance(v, dependencies.ExternalProgram): - return ExternalProgramHolder(v) + return ExternalProgramHolder(v, self.subproject) elif isinstance(v, dependencies.InternalDependency): # FIXME: This is special cased and not ideal: # The first source is our new VapiTarget, the rest are deps @@ -2520,7 +2609,7 @@ external dependencies (including libraries) must go to "dependencies".''') @noKwargs def func_assert(self, node, args, kwargs): if len(args) == 1: - FeatureNew('assert function without message argument', '0.53.0').use(self.subproject) + FeatureNew.single_use('assert function without message argument', '0.53.0', self.subproject) value = args[0] message = None elif len(args) == 2: @@ -2856,7 +2945,7 @@ external dependencies (including libraries) must go to "dependencies".''') if len(args) > 1: raise InterpreterException('configuration_data takes only one optional positional arguments') elif len(args) == 1: - FeatureNew('configuration_data dictionary', '0.49.0').use(self.subproject) + FeatureNew.single_use('configuration_data dictionary', '0.49.0', self.subproject) initial_values = args[0] if not isinstance(initial_values, dict): raise InterpreterException('configuration_data first argument must be a dictionary') @@ -2896,11 +2985,14 @@ external dependencies (including libraries) must go to "dependencies".''') if ':' in proj_name: raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name)) + # This needs to be evaluated as early as possible, as meson uses this + # for things like deprecation testing. if 'meson_version' in kwargs: cv = coredata.version pv = kwargs['meson_version'] if not mesonlib.version_compare(cv, pv): raise InterpreterException('Meson version is %s but project requires %s' % (cv, pv)) + mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version'] if os.path.exists(self.option_file): oi = optinterpreter.OptionInterpreter(self.subproject) @@ -2947,10 +3039,6 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.subproject_dir = self.subproject_dir - mesonlib.project_meson_versions[self.subproject] = '' - if 'meson_version' in kwargs: - mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version'] - self.build.projects[self.subproject] = proj_name mlog.log('Project name:', mlog.bold(proj_name)) mlog.log('Project version:', mlog.bold(self.project_version)) @@ -2997,7 +3085,7 @@ external dependencies (including libraries) must go to "dependencies".''') @noKwargs def func_message(self, node, args, kwargs): if len(args) > 1: - FeatureNew('message with more than one argument', '0.54.0').use(self.subproject) + FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject) args_str = [self.get_message_string_arg(i) for i in args] self.message_impl(args_str) @@ -3059,7 +3147,7 @@ external dependencies (including libraries) must go to "dependencies".''') @noKwargs def func_warning(self, node, args, kwargs): if len(args) > 1: - FeatureNew('warning with more than one argument', '0.54.0').use(self.subproject) + FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject) args_str = [self.get_message_string_arg(i) for i in args] mlog.warning(*args_str, location=node) @@ -3091,6 +3179,12 @@ external dependencies (including libraries) must go to "dependencies".''') return should def add_languages_for(self, args, required, for_machine: MachineChoice): + langs = set(self.coredata.compilers[for_machine].keys()) + langs.update(args) + if 'vala' in langs: + if 'c' not in langs: + raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.') + success = True for lang in sorted(args, key=compilers.sort_clink): lang = lang.lower() @@ -3128,11 +3222,6 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version) self.build.ensure_static_linker(comp) - langs = self.coredata.compilers[for_machine].keys() - if 'vala' in langs: - if 'c' not in langs: - raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.') - return success def program_from_file_for(self, for_machine, prognames, silent): @@ -3143,7 +3232,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Executable name must be a string') prog = ExternalProgram.from_bin_list(self.environment, for_machine, p) if prog.found(): - return ExternalProgramHolder(prog) + return ExternalProgramHolder(prog, self.subproject) return None def program_from_system(self, args, search_dirs, silent=False): @@ -3170,7 +3259,7 @@ external dependencies (including libraries) must go to "dependencies".''') extprog = dependencies.ExternalProgram(exename, search_dir=search_dir, extra_search_dirs=extra_search_dirs, silent=silent) - progobj = ExternalProgramHolder(extprog) + progobj = ExternalProgramHolder(extprog, self.subproject) if progobj.found(): return progobj @@ -3183,7 +3272,7 @@ external dependencies (including libraries) must go to "dependencies".''') if not silent: mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), '(overridden: %s)' % exe.description()) - return ExternalProgramHolder(exe) + return ExternalProgramHolder(exe, self.subproject, self.backend) return None def store_name_lookups(self, command_names): @@ -3214,11 +3303,11 @@ external dependencies (including libraries) must go to "dependencies".''') progobj = self.program_from_system(args, search_dirs, silent=silent) if progobj is None and args[0].endswith('python3'): prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True) - progobj = ExternalProgramHolder(prog) + progobj = ExternalProgramHolder(prog, self.subproject) if required and (progobj is None or not progobj.found()): raise InvalidArguments('Program(s) {!r} not found or not executable'.format(args)) if progobj is None: - return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args))) + return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) # Only store successful lookups self.store_name_lookups(args) if wanted: @@ -3231,7 +3320,7 @@ external dependencies (including libraries) must go to "dependencies".''') 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(' '.join(args))) + return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) return progobj @FeatureNewKwargs('find_program', '0.53.0', ['dirs']) @@ -3246,7 +3335,7 @@ external dependencies (including libraries) must go to "dependencies".''') disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled') - return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args))) + return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) search_dirs = extract_search_dirs(kwargs) wanted = mesonlib.stringlistify(kwargs.get('version', [])) @@ -3261,7 +3350,7 @@ external dependencies (including libraries) must go to "dependencies".''') 'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n' ) - def _find_cached_dep(self, name, kwargs): + def _find_cached_dep(self, name, display_name, kwargs): # Check if we want this as a build-time / build machine or runt-time / # host machine dep. for_machine = self.machine_from_native_kwarg(kwargs) @@ -3276,7 +3365,7 @@ external dependencies (including libraries) must go to "dependencies".''') # have explicitly called meson.override_dependency() with a not-found # dep. if not cached_dep.found(): - mlog.log('Dependency', mlog.bold(name), + mlog.log('Dependency', mlog.bold(display_name), 'found:', mlog.red('NO'), *info) return identifier, cached_dep found_vers = cached_dep.get_version() @@ -3298,7 +3387,7 @@ external dependencies (including libraries) must go to "dependencies".''') if cached_dep: if found_vers: info = [mlog.normal_cyan(found_vers), *info] - mlog.log('Dependency', mlog.bold(name), + mlog.log('Dependency', mlog.bold(display_name), 'found:', mlog.green('YES'), *info) return identifier, cached_dep @@ -3321,7 +3410,7 @@ external dependencies (including libraries) must go to "dependencies".''') return dep = subi.get_variable_method([varname], {}) if dep.held_object != cached_dep: - m = 'Inconsistency: Subproject has overriden the dependency with another variable than {!r}' + m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}' raise DependencyException(m.format(varname)) def get_subproject_dep(self, name, display_name, dirname, varname, kwargs): @@ -3331,9 +3420,9 @@ external dependencies (including libraries) must go to "dependencies".''') dep = self.notfound_dependency() try: subproject = self.subprojects[dirname] - _, cached_dep = self._find_cached_dep(name, kwargs) + _, cached_dep = self._find_cached_dep(name, display_name, kwargs) if varname is None: - # Assuming the subproject overriden the dependency we want + # Assuming the subproject overridden the dependency we want if cached_dep: if required and not cached_dep.found(): m = 'Dependency {!r} is not satisfied' @@ -3382,15 +3471,15 @@ external dependencies (including libraries) must go to "dependencies".''') def _handle_featurenew_dependencies(self, name): 'Do a feature check on dependencies used by this subproject' if name == 'mpi': - FeatureNew('MPI Dependency', '0.42.0').use(self.subproject) + FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject) elif name == 'pcap': - FeatureNew('Pcap Dependency', '0.42.0').use(self.subproject) + FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject) elif name == 'vulkan': - FeatureNew('Vulkan Dependency', '0.42.0').use(self.subproject) + FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject) elif name == 'libwmf': - FeatureNew('LibWMF Dependency', '0.44.0').use(self.subproject) + FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject) elif name == 'openmp': - FeatureNew('OpenMP Dependency', '0.46.0').use(self.subproject) + FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject) @FeatureNewKwargs('dependency', '0.54.0', ['components']) @FeatureNewKwargs('dependency', '0.52.0', ['include_type']) @@ -3404,6 +3493,9 @@ external dependencies (including libraries) must go to "dependencies".''') self.validate_arguments(args, 1, [str]) name = args[0] display_name = name if name else '(anonymous)' + mods = extract_as_list(kwargs, 'modules') + if mods: + display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods)) not_found_message = kwargs.get('not_found_message', '') if not isinstance(not_found_message, str): raise InvalidArguments('The not_found_message must be a string.') @@ -3445,7 +3537,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify' 'version\n requirements use the \'version\' keyword argument instead.') - identifier, cached_dep = self._find_cached_dep(name, kwargs) + identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs) if cached_dep: if has_fallback: dirname, varname = self.get_subproject_infos(kwargs) @@ -3509,7 +3601,7 @@ external dependencies (including libraries) must go to "dependencies".''') def get_subproject_infos(self, kwargs): fbinfo = mesonlib.stringlistify(kwargs['fallback']) if len(fbinfo) == 1: - FeatureNew('Fallback without variable name', '0.53.0').use(self.subproject) + FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject) return fbinfo[0], None elif len(fbinfo) != 2: raise InterpreterException('Fallback info must have one or two items.') @@ -3597,11 +3689,13 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Unknown target_type.') @permittedKwargs(permitted_kwargs['vcs_tag']) + @FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'], + 'combine build_by_default and build_always_stale instead.') def func_vcs_tag(self, node, args, kwargs): if 'input' not in kwargs or 'output' not in kwargs: raise InterpreterException('Keyword arguments input and output must exist') if 'fallback' not in kwargs: - FeatureNew('Optional fallback in vcs_tag', '0.41.0').use(self.subproject) + FeatureNew.single_use('Optional fallback in vcs_tag', '0.41.0', self.subproject) fallback = kwargs.pop('fallback', self.project_version) if not isinstance(fallback, str): raise InterpreterException('Keyword argument fallback must be a string.') @@ -3654,7 +3748,7 @@ external dependencies (including libraries) must go to "dependencies".''') if len(args) != 1: raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name') if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): - FeatureNew('substitutions in custom_target depfile', '0.47.0').use(self.subproject) + FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject) return self._func_custom_target_impl(node, args, kwargs) def _func_custom_target_impl(self, node, args, kwargs): @@ -3742,6 +3836,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self @FeatureNewKwargs('test', '0.52.0', ['priority']) @permittedKwargs(permitted_kwargs['test']) def func_test(self, node, args, kwargs): + if kwargs.get('protocol') == 'gtest': + FeatureNew.single_use('"gtest" protocol for tests', '0.55.0', self.subproject) self.add_test(node, args, kwargs, True) def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables: @@ -3749,7 +3845,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if isinstance(envlist, EnvironmentVariablesHolder): env = envlist.held_object elif isinstance(envlist, dict): - FeatureNew('environment dictionary', '0.52.0').use(self.subproject) + FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject) env = EnvironmentVariablesHolder(envlist) env = env.held_object else: @@ -3761,7 +3857,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self def add_test(self, node, args, kwargs, is_base_test): if len(args) != 2: - raise InterpreterException('Incorrect number of arguments') + raise InterpreterException('test expects 2 arguments, {} given'.format(len(args))) if not isinstance(args[0], str): raise InterpreterException('First argument of test must be a string.') exe = args[1] @@ -3793,8 +3889,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if not isinstance(timeout, int): raise InterpreterException('Timeout must be an integer.') protocol = kwargs.get('protocol', 'exitcode') - if protocol not in ('exitcode', 'tap'): - raise InterpreterException('Protocol must be "exitcode" or "tap".') + if protocol not in {'exitcode', 'tap', 'gtest'}: + raise InterpreterException('Protocol must be "exitcode", "tap", or "gtest".') suite = [] prj = self.subproject if self.is_subproject() else self.build.project_name for s in mesonlib.stringlistify(kwargs.get('suite', '')): @@ -3868,7 +3964,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self absname = os.path.join(self.environment.get_source_dir(), buildfilename) if not os.path.isfile(absname): self.subdir = prev_subdir - raise InterpreterException('Non-existent build file {!r}'.format(buildfilename)) + raise InterpreterException("Non-existent build file '{!s}'".format(buildfilename)) with open(absname, encoding='utf8') as f: code = f.read() assert(isinstance(code, str)) @@ -3916,7 +4012,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self elif isinstance(s, str): source_strings.append(s) else: - raise InvalidArguments('Argument {!r} must be string or file.'.format(s)) + raise InvalidArguments('Argument must be string or file.') sources += self.source_strings_to_files(source_strings) install_dir = kwargs.get('install_dir', None) if not isinstance(install_dir, (str, type(None))): @@ -4065,7 +4161,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self if 'configuration' in kwargs: conf = kwargs['configuration'] if isinstance(conf, dict): - FeatureNew('configure_file.configuration dictionary', '0.49.0').use(self.subproject) + FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject) conf = ConfigurationDataHolder(self.subproject, conf) elif not isinstance(conf, ConfigurationDataHolder): raise InterpreterException('Argument "configuration" is not of type configuration_data') @@ -4095,7 +4191,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self conf.mark_used() elif 'command' in kwargs: if len(inputs) > 1: - FeatureNew('multiple inputs in configure_file()', '0.52.0').use(self.subproject) + FeatureNew.single_use('multiple inputs in configure_file()', '0.52.0', self.subproject) # We use absolute paths for input and output here because the cwd # that the command is run from is 'unspecified', so it could change. # Currently it's builddir/subdir for in_builddir else srcdir/subdir. @@ -4190,8 +4286,9 @@ This will become a hard error in the future.''' % kwargs['input'], location=self for a in incdir_strings: if a.startswith(src_root): - raise InvalidArguments('''Tried to form an absolute path to a source dir. You should not do that but use -relative paths instead. + raise InvalidArguments('Tried to form an absolute path to a source dir. ' + 'You should not do that but use relative paths instead.' + ''' To get include path to any directory relative to the current dir do @@ -4342,7 +4439,7 @@ different subdirectory. if len(args) > 1: raise InterpreterException('environment takes only one optional positional arguments') elif len(args) == 1: - FeatureNew('environment positional arguments', '0.52.0').use(self.subproject) + FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject) initial_values = args[0] if not isinstance(initial_values, dict) and not isinstance(initial_values, list): raise InterpreterException('environment first argument must be a dictionary or a list') @@ -4551,6 +4648,7 @@ Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_s kwargs['include_directories'] = self.extract_incdirs(kwargs) target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs) + target.project_version = self.project_version if not self.environment.machines.matches_build_machine(for_machine): self.add_cross_stdlib_info(target) @@ -4631,6 +4729,8 @@ This will become a hard error in the future.''', location=self.current_node) if len(args) < 1 or len(args) > 2: raise InvalidCode('Get_variable takes one or two arguments.') varname = args[0] + if isinstance(varname, Disabler): + return varname if not isinstance(varname, str): raise InterpreterException('First argument must be a string.') try: diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 1a7aa38..634f4f2 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -18,6 +18,7 @@ from . import mparser, mesonlib, mlog from . import environment, dependencies +import abc import os, copy, re import collections.abc from functools import wraps @@ -212,17 +213,17 @@ class permittedKwargs: return f(*wrapped_args, **wrapped_kwargs) return wrapped -class FeatureCheckBase: +class FeatureCheckBase(metaclass=abc.ABCMeta): "Base class for feature version checks" - # Class variable, shared across all instances - # - # Format: {subproject: {feature_version: set(feature_names)}} + # In python 3.6 we can just forward declare this, but in 3.5 we can't + # This will be overwritten by the subclasses by necessity feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] - def __init__(self, feature_name: str, version: str): + def __init__(self, feature_name: str, version: str, extra_message: T.Optional[str] = None): self.feature_name = feature_name # type: str self.feature_version = version # type: str + self.extra_message = extra_message or '' # type: str @staticmethod def get_target_version(subproject: str) -> str: @@ -231,13 +232,18 @@ class FeatureCheckBase: return '' return mesonlib.project_meson_versions[subproject] + @staticmethod + @abc.abstractmethod + def check_version(target_version: str, feature_Version: str) -> bool: + pass + def use(self, subproject: str) -> None: tv = self.get_target_version(subproject) # No target version if tv == '': return # Target version is new enough - if mesonlib.version_compare_condition_with_min(tv, self.feature_version): + if self.check_version(tv, self.feature_version): return # Feature is too new for target version, register it if subproject not in self.feature_registry: @@ -280,41 +286,86 @@ class FeatureCheckBase: return f(*wrapped_args, **wrapped_kwargs) return wrapped + @classmethod + def single_use(cls, feature_name: str, version: str, subproject: str, + extra_message: T.Optional[str] = None) -> None: + """Oneline version that instantiates and calls use().""" + cls(feature_name, version, extra_message).use(subproject) + + class FeatureNew(FeatureCheckBase): """Checks for new features""" + # Class variable, shared across all instances + # + # Format: {subproject: {feature_version: set(feature_names)}} + feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] + + @staticmethod + def check_version(target_version: str, feature_version: str) -> bool: + return mesonlib.version_compare_condition_with_min(target_version, feature_version) + @staticmethod def get_warning_str_prefix(tv: str) -> str: return 'Project specifies a minimum meson_version \'{}\' but uses features which were added in newer versions:'.format(tv) def log_usage_warning(self, tv: str) -> None: - mlog.warning('Project targeting \'{}\' but tried to use feature introduced ' - 'in \'{}\': {}'.format(tv, self.feature_version, self.feature_name)) + args = [ + 'Project targeting', "'{}'".format(tv), + 'but tried to use feature introduced in', + "'{}':".format(self.feature_version), + '{}.'.format(self.feature_name), + ] + if self.extra_message: + args.append(self.extra_message) + mlog.warning(*args) class FeatureDeprecated(FeatureCheckBase): """Checks for deprecated features""" + # Class variable, shared across all instances + # + # Format: {subproject: {feature_version: set(feature_names)}} + feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[str]]]] + + @staticmethod + def check_version(target_version: str, feature_version: str) -> bool: + # For deprecatoin checks we need to return the inverse of FeatureNew checks + return not mesonlib.version_compare_condition_with_min(target_version, feature_version) + @staticmethod def get_warning_str_prefix(tv: str) -> str: return 'Deprecated features used:' def log_usage_warning(self, tv: str) -> None: - mlog.deprecation('Project targeting \'{}\' but tried to use feature ' - 'deprecated since \'{}\': {}' - ''.format(tv, self.feature_version, self.feature_name)) - - -class FeatureCheckKwargsBase: - def __init__(self, feature_name: str, feature_version: str, kwargs: T.List[str]): + args = [ + 'Project targeting', "'{}'".format(tv), + 'but tried to use feature deprecated since', + "'{}':".format(self.feature_version), + '{}.'.format(self.feature_name), + ] + if self.extra_message: + args.append(self.extra_message) + mlog.warning(*args) + + +class FeatureCheckKwargsBase(metaclass=abc.ABCMeta): + + @property + @abc.abstractmethod + def feature_check_class(self) -> T.Type[FeatureCheckBase]: + pass + + def __init__(self, feature_name: str, feature_version: str, + kwargs: T.List[str], extra_message: T.Optional[str] = None): self.feature_name = feature_name self.feature_version = feature_version self.kwargs = kwargs + self.extra_message = extra_message def __call__(self, f): @wraps(f) def wrapped(*wrapped_args, **wrapped_kwargs): - # Which FeatureCheck class to invoke - FeatureCheckClass = self.feature_check_class kwargs, subproject = _get_callee_args(wrapped_args, want_subproject=True)[3:5] if subproject is None: raise AssertionError('{!r}'.format(wrapped_args)) @@ -322,7 +373,8 @@ class FeatureCheckKwargsBase: if arg not in kwargs: continue name = arg + ' arg in ' + self.feature_name - FeatureCheckClass(name, self.feature_version).use(subproject) + self.feature_check_class.single_use( + name, self.feature_version, subproject, self.extra_message) return f(*wrapped_args, **wrapped_kwargs) return wrapped @@ -532,7 +584,7 @@ class InterpreterBase: self.argument_depth += 1 for key, value in kwargs.items(): if not isinstance(key, mparser.StringNode): - FeatureNew('Dictionary entry using non literal key', '0.53.0').use(self.subproject) + FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject) assert isinstance(key, mparser.BaseNode) # All keys must be nodes due to resolve_key_nodes=False str_key = self.evaluate_statement(key) if not isinstance(str_key, str): @@ -819,7 +871,7 @@ The result of this is undefined and will become a hard error in a future Meson r def function_call(self, node: mparser.FunctionNode) -> T.Optional[TYPE_var]: func_name = node.func_name (posargs, kwargs) = self.reduce_arguments(node.args) - if is_disabled(posargs, kwargs) and func_name != 'set_variable' and func_name != 'is_disabler': + if is_disabled(posargs, kwargs) and func_name not in {'get_variable', 'set_variable', 'is_disabler'}: return Disabler() if func_name in self.funcs: func = self.funcs[func_name] diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 44c720f..f02c297 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -56,8 +56,8 @@ class StaticLinker: def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: - return [] + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) def thread_link_flags(self, env: 'Environment') -> T.List[str]: return [] @@ -444,8 +444,8 @@ class DynamicLinker(LinkerEnvVarsMixin, metaclass=abc.ABCMeta): def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: - return [] + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], @@ -551,12 +551,12 @@ class GnuLikeDynamicLinkerMixin: def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: m = env.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): - return [] + return ([], set()) if not rpath_paths and not install_rpath and not build_rpath: - return [] + return ([], set()) args = [] origin_placeholder = '$ORIGIN' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) @@ -564,9 +564,14 @@ class GnuLikeDynamicLinkerMixin: # is *very* allergic to duplicate -delete_rpath arguments # when calling depfixer on installation. all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) + rpath_dirs_to_remove = set() + for p in all_paths: + rpath_dirs_to_remove.add(p.encode('utf8')) # Build_rpath is used as-is (it is usually absolute). if build_rpath != '': all_paths.add(build_rpath) + for p in build_rpath.split(':'): + rpath_dirs_to_remove.add(p.encode('utf8')) # TODO: should this actually be "for (dragonfly|open)bsd"? if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd(): @@ -590,7 +595,7 @@ class GnuLikeDynamicLinkerMixin: # TODO: should this actually be "for solaris/sunos"? if mesonlib.is_sunos(): - return args + return (args, rpath_dirs_to_remove) # Rpaths to use while linking must be absolute. These are not # written to the binary. Needed only with GNU ld: @@ -610,7 +615,7 @@ class GnuLikeDynamicLinkerMixin: for p in rpath_paths: args.extend(self._apply_prefix('-rpath-link,' + os.path.join(build_dir, p))) - return args + return (args, rpath_dirs_to_remove) class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): @@ -676,9 +681,9 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: if not rpath_paths and not install_rpath and not build_rpath: - return [] + return ([], set()) # Ensure that there is enough space for install_name_tool in-place # editing of large RPATHs args = self._apply_prefix('-headerpad_max_install_names') @@ -692,7 +697,7 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): for rp in all_paths: args.extend(self._apply_prefix('-rpath,' + rp)) - return args + return (args, set()) class GnuDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): @@ -761,6 +766,11 @@ class WASMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, Dyna def get_asneeded_args(self) -> T.List[str]: return [] + def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, + rpath_paths: str, build_rpath: str, + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) + class CcrxDynamicLinker(DynamicLinker): @@ -834,8 +844,8 @@ class Xc16DynamicLinker(DynamicLinker): def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: - return [] + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + return ([], set()) class C2000DynamicLinker(DynamicLinker): @@ -933,10 +943,10 @@ class PGIDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: if not env.machines[self.for_machine].is_windows(): - return ['-R' + os.path.join(build_dir, p) for p in rpath_paths] - return [] + return (['-R' + os.path.join(build_dir, p) for p in rpath_paths], set()) + return ([], set()) class PGIStaticLinker(StaticLinker): @@ -1086,9 +1096,9 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, - install_rpath: str) -> T.List[str]: + install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: if not rpath_paths and not install_rpath and not build_rpath: - return [] + return ([], set()) processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths]) if build_rpath != '': @@ -1103,7 +1113,7 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): paths = padding else: paths = paths + ':' + padding - return self._apply_prefix('-rpath,{}'.format(paths)) + return (self._apply_prefix('-rpath,{}'.format(paths)), set()) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py index 7829ffc..e457623 100644 --- a/mesonbuild/mcompile.py +++ b/mesonbuild/mcompile.py @@ -14,20 +14,71 @@ """Entrypoint script for backend agnostic compile.""" -import os -import pathlib -import shutil import sys import typing as T +from pathlib import Path from . import mlog from . import mesonlib +from . import coredata from .mesonlib import MesonException +from mesonbuild.environment import detect_ninja if T.TYPE_CHECKING: import argparse + +def validate_builddir(builddir: Path): + if not (builddir / 'meson-private' / 'coredata.dat' ).is_file(): + raise MesonException('Current directory is not a meson build directory: `{}`.\n' + 'Please specify a valid build dir or change the working directory to it.\n' + 'It is also possible that the build directory was generated with an old\n' + 'meson version. Please regenerate it in this case.'.format(builddir)) + +def get_backend_from_coredata(builddir: Path) -> str: + """ + Gets `backend` option value from coredata + """ + return coredata.load(str(builddir)).get_builtin_option('backend') + +def get_parsed_args_ninja(options: 'argparse.Namespace', builddir: Path): + runner = detect_ninja() + if runner is None: + raise MesonException('Cannot find ninja.') + mlog.log('Found runner:', runner) - + cmd = [runner, '-C', builddir.as_posix()] + + # If the value is set to < 1 then don't set anything, which let's + # ninja/samu decide what to do. + if options.jobs > 0: + cmd.extend(['-j', str(options.jobs)]) + if options.load_average > 0: + cmd.extend(['-l', str(options.load_average)]) + if options.clean: + cmd.append('clean') + + return cmd + +def get_parsed_args_vs(options: 'argparse.Namespace', builddir: Path): + slns = list(builddir.glob('*.sln')) + assert len(slns) == 1, 'More than one solution in a project?' + + sln = slns[0] + cmd = ['msbuild', str(sln.resolve())] + + # In msbuild `-m` with no number means "detect cpus", the default is `-m1` + if options.jobs > 0: + cmd.append('-m{}'.format(options.jobs)) + else: + cmd.append('-m') + + if options.load_average: + mlog.warning('Msbuild does not have a load-average switch, ignoring.') + if options.clean: + cmd.extend(['/t:Clean']) + + return cmd + def add_arguments(parser: 'argparse.ArgumentParser') -> None: """Add compile specific arguments.""" parser.add_argument( @@ -53,69 +104,27 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None: '-C', action='store', dest='builddir', - type=pathlib.Path, + type=Path, default='.', help='The directory containing build files to be built.' ) def run(options: 'argparse.Namespace') -> int: - bdir = options.builddir # type: pathlib.Path - if not bdir.exists(): - raise MesonException('Path to builddir {} does not exist!'.format(str(bdir.resolve()))) - if not bdir.is_dir(): - raise MesonException('builddir path should be a directory.') + bdir = options.builddir # type: Path + validate_builddir(bdir.resolve()) cmd = [] # type: T.List[str] - runner = None # type T.Optional[str] - slns = list(bdir.glob('*.sln')) - - if (bdir / 'build.ninja').exists(): - runner = os.environ.get('NINJA') - if not runner: - if shutil.which('ninja'): - runner = 'ninja' - elif shutil.which('samu'): - runner = 'samu' - - if runner is None: - raise MesonException('Cannot find either ninja or samu.') - - cmd = [runner, '-C', bdir.as_posix()] - - # If the value is set to < 1 then don't set anything, which let's - # ninja/samu decide what to do. - if options.jobs > 0: - cmd.extend(['-j', str(options.jobs)]) - if options.load_average > 0: - cmd.extend(['-l', str(options.load_average)]) - if options.clean: - cmd.append('clean') - - # TODO: with python 3.8 this could be `elif slns := bdir.glob('*.sln'):` - elif slns: - assert len(slns) == 1, 'More than one solution in a project?' - - sln = slns[0] - cmd = ['msbuild', str(sln.resolve())] - - # In msbuild `-m` with no number means "detect cpus", the default is `-m1` - if options.jobs > 0: - cmd.append('-m{}'.format(options.jobs)) - else: - cmd.append('-m') - - if options.load_average: - mlog.warning('Msbuild does not have a load-average switch, ignoring.') - if options.clean: - cmd.extend(['/t:Clean']) - - # TODO: xcode? + + backend = get_backend_from_coredata(bdir) + if backend == 'ninja': + cmd = get_parsed_args_ninja(options, bdir) + elif backend.startswith('vs'): + cmd = get_parsed_args_vs(options, bdir) else: + # TODO: xcode? raise MesonException( - 'Could not find any runner or backend for directory {}'.format(bdir.resolve().as_posix())) - - mlog.log('Found runner:', runner) + 'Backend `{}` is not yet supported by `compile`. Use generated project files directly instead.'.format(backend)) p, *_ = mesonlib.Popen_safe(cmd, stdout=sys.stdout.buffer, stderr=sys.stderr.buffer) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 6c1e466..26fe6eb 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -39,8 +39,10 @@ _U = T.TypeVar('_U') have_fcntl = False have_msvcrt = False +# TODO: this is such a hack, this really should be either in coredata or in the +# interpreter # {subproject: project_meson_version} -project_meson_versions = {} # type: T.Dict[str, str] +project_meson_versions = collections.defaultdict(str) # type: T.DefaultDict[str, str] try: import fcntl @@ -1533,6 +1535,16 @@ def relpath(path: str, start: str) -> str: except (TypeError, ValueError): return path +def path_is_in_root(path: Path, root: Path, resolve: bool = False) -> bool: + # Check wheter a path is within the root directory root + try: + if resolve: + path.resolve().relative_to(root.resolve()) + else: + path.relative_to(root) + except ValueError: + return False + return True class LibType(Enum): diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 9c64429..0be01fe 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -512,7 +512,7 @@ class Installer: if file_copied: self.did_install_something = True try: - depfixer.fix_rpath(outname, install_rpath, final_path, + depfixer.fix_rpath(outname, t.rpath_dirs_to_remove, install_rpath, final_path, install_name_mappings, verbose=False) except SystemExit as e: if isinstance(e.code, int) and e.code == 0: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index d5516d4..8eb659b 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -22,7 +22,7 @@ project files and don't need this info.""" import json from . import build, coredata as cdata from . import mesonlib -from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator +from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter from . import mlog from .backend import backends from .mparser import BaseNode, FunctionNode, ArrayNode, ArgumentNode, StringNode @@ -62,6 +62,7 @@ def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None, benchmarkdata = testdata = installdata = None return { + 'ast': IntroCommand('Dump the AST of the meson file', no_bd=dump_ast), 'benchmarks': IntroCommand('List all benchmarks', func=lambda: list_benchmarks(benchmarkdata)), 'buildoptions': IntroCommand('List all build options', func=lambda: list_buildoptions(coredata), no_bd=list_buildoptions_from_source), 'buildsystem_files': IntroCommand('List files that make up the build system', func=lambda: list_buildsystem_files(builddata, interpreter)), @@ -89,6 +90,11 @@ def add_arguments(parser): help='Always use the new JSON format for multiple entries (even for 0 and 1 introspection commands)') parser.add_argument('builddir', nargs='?', default='.', help='The build directory') +def dump_ast(intr: IntrospectionInterpreter) -> T.Dict[str, T.Any]: + printer = AstJSONPrinter() + intr.ast.accept(printer) + return printer.result + def list_installed(installdata): res = {} if installdata is not None: @@ -328,7 +334,7 @@ def get_test_list(testdata) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], to['suite'] = t.suite to['is_parallel'] = t.is_parallel to['priority'] = t.priority - to['protocol'] = t.protocol + to['protocol'] = str(t.protocol) result.append(to) return result diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index 8cbd248..7b8aec7 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -40,15 +40,32 @@ def _windows_ansi() -> bool: # original behavior return bool(kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON')) -def setup_console() -> bool: +def colorize_console() -> bool: + _colorize_console = getattr(sys.stdout, 'colorize_console', None) # type: bool + if _colorize_console is not None: + return _colorize_console + try: if platform.system().lower() == 'windows': - return os.isatty(sys.stdout.fileno()) and _windows_ansi() - return os.isatty(sys.stdout.fileno()) and os.environ.get('TERM') != 'dumb' + _colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi() + else: + _colorize_console = os.isatty(sys.stdout.fileno()) and os.environ.get('TERM', 'dumb') != 'dumb' except Exception: - return False + _colorize_console = False + + sys.stdout.colorize_console = _colorize_console # type: ignore[attr-defined] + return _colorize_console + +def setup_console(): + # on Windows, a subprocess might call SetConsoleMode() on the console + # connected to stdout and turn off ANSI escape processing. Call this after + # running a subprocess to ensure we turn it on again. + if platform.system().lower() == 'windows': + try: + delattr(sys.stdout, 'colorize_console') + except AttributeError: + pass -colorize_console = setup_console() log_dir = None # type: T.Optional[str] log_file = None # type: T.Optional[T.TextIO] log_fname = 'meson-log.txt' # type: str @@ -204,7 +221,7 @@ def log(*args: T.Union[str, AnsiDecorator], is_error: bool = False, if log_file is not None: print(*arr, file=log_file, **kwargs) log_file.flush() - if colorize_console: + if colorize_console(): arr = process_markup(args, True) if not log_errors_only or is_error: force_print(*arr, **kwargs) diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index dc86a1b..47be039 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -57,6 +57,17 @@ def get_include_args(include_dirs, prefix='-I'): return dirs_str +def is_module_library(fname): + ''' + Check if the file is a library-like file generated by a module-specific + target, such as GirTarget or TypelibTarget + ''' + if hasattr(fname, 'fname'): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in ('gir', 'typelib') + + class ModuleReturnValue: def __init__(self, return_value, new_objects): self.return_value = return_value diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 8317629..ea1b325 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -32,8 +32,8 @@ from ..mesonlib import ( MachineChoice, MesonException, OrderedSet, Popen_safe, extract_as_list, join_args, unholder, ) -from ..dependencies import Dependency, PkgConfigDependency, InternalDependency -from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs +from ..dependencies import Dependency, PkgConfigDependency, InternalDependency, ExternalProgram +from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs # gresource compilation is broken due to the way # the resource compiler and Ninja clash about it @@ -44,20 +44,6 @@ gresource_dep_needed_version = '>= 2.51.1' native_glib_version = None -@functools.lru_cache(maxsize=None) -def gir_has_option(intr_obj, option): - try: - g_ir_scanner = intr_obj.find_program_impl('g-ir-scanner') - # Handle overridden g-ir-scanner - if isinstance(getattr(g_ir_scanner, "held_object", g_ir_scanner), interpreter.OverrideProgram): - assert option in ['--extra-library', '--sources-top-dirs'] - return True - - opts = Popen_safe(g_ir_scanner.get_command() + ['--help'], stderr=subprocess.STDOUT)[1] - return option in opts - except (MesonException, FileNotFoundError, subprocess.CalledProcessError): - return False - class GnomeModule(ExtensionModule): gir_dep = None @@ -303,7 +289,7 @@ class GnomeModule(ExtensionModule): link_command.append('-L' + d) if include_rpath: link_command.append('-Wl,-rpath,' + d) - if gir_has_option(self.interpreter, '--extra-library') and use_gir_args: + if use_gir_args and self._gir_has_option('--extra-library'): link_command.append('--extra-library=' + lib.name) else: link_command.append('-l' + lib.name) @@ -321,6 +307,10 @@ class GnomeModule(ExtensionModule): deps = mesonlib.unholder(mesonlib.listify(deps)) for dep in deps: + if isinstance(dep, Dependency): + girdir = dep.get_variable(pkgconfig='girdir', internal='girdir', default_value='') + if girdir: + gi_includes.update([girdir]) if isinstance(dep, InternalDependency): cflags.update(dep.get_compile_args()) cflags.update(get_include_args(dep.include_directories)) @@ -371,11 +361,6 @@ class GnomeModule(ExtensionModule): external_ldflags_nodedup += [lib, next(ldflags)] else: external_ldflags.update([lib]) - - if isinstance(dep, PkgConfigDependency): - girdir = dep.get_pkgconfig_variable("girdir", {'default': ''}) - if girdir: - gi_includes.update([girdir]) elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): cflags.update(get_include_args(dep.get_include_dirs())) depends.append(dep) @@ -383,7 +368,7 @@ class GnomeModule(ExtensionModule): mlog.log('dependency {!r} not handled to build gir files'.format(dep)) continue - if gir_has_option(self.interpreter, '--extra-library') and use_gir_args: + if use_gir_args and self._gir_has_option('--extra-library'): def fix_ldflags(ldflags): fixed_ldflags = OrderedSet() for ldflag in ldflags: @@ -417,15 +402,37 @@ class GnomeModule(ExtensionModule): return girtarget def _get_gir_dep(self, state): - try: - gir_dep = self.gir_dep or PkgConfigDependency('gobject-introspection-1.0', - state.environment, - {'native': True}) - pkgargs = gir_dep.get_compile_args() - except Exception: - raise MesonException('gobject-introspection dependency was not found, gir cannot be generated.') - - return gir_dep, pkgargs + if not self.gir_dep: + kwargs = {'native': True, 'required': True} + holder = self.interpreter.func_dependency(state.current_node, ['gobject-introspection-1.0'], kwargs) + self.gir_dep = holder.held_object + giscanner = state.environment.lookup_binary_entry(MachineChoice.HOST, 'g-ir-scanner') + if giscanner is not None: + self.giscanner = ExternalProgram.from_entry('g-ir-scanner', giscanner) + elif self.gir_dep.type_name == 'pkgconfig': + self.giscanner = ExternalProgram('g_ir_scanner', self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {})) + else: + self.giscanner = self.interpreter.find_program_impl('g-ir-scanner') + gicompiler = state.environment.lookup_binary_entry(MachineChoice.HOST, 'g-ir-compiler') + if gicompiler is not None: + self.gicompiler = ExternalProgram.from_entry('g-ir-compiler', gicompiler) + elif self.gir_dep.type_name == 'pkgconfig': + self.gicompiler = ExternalProgram('g_ir_compiler', self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {})) + else: + self.gicompiler = self.interpreter.find_program_impl('g-ir-compiler') + return self.gir_dep, self.giscanner, self.gicompiler + + @functools.lru_cache(maxsize=None) + def _gir_has_option(self, option): + exe = self.giscanner + if hasattr(exe, 'held_object'): + exe = exe.held_object + if isinstance(exe, interpreter.OverrideProgram): + # Handle overridden g-ir-scanner + assert option in ['--extra-library', '--sources-top-dirs'] + return True + p, o, e = Popen_safe(exe.get_command() + ['--help'], stderr=subprocess.STDOUT) + return p.returncode == 0 and option in o def _scan_header(self, kwargs): ret = [] @@ -688,11 +695,10 @@ class GnomeModule(ExtensionModule): source.get_subdir()) if subdir not in typelib_includes: typelib_includes.append(subdir) - elif isinstance(dep, PkgConfigDependency): - girdir = dep.get_pkgconfig_variable("girdir", {'default': ''}) + if isinstance(dep, Dependency): + girdir = dep.get_variable(pkgconfig='girdir', internal='girdir', default_value='') if girdir and girdir not in typelib_includes: typelib_includes.append(girdir) - return typelib_includes def _get_external_args_for_langs(self, state, langs): @@ -731,42 +737,25 @@ class GnomeModule(ExtensionModule): if len(girtargets) > 1 and any([isinstance(el, build.Executable) for el in girtargets]): raise MesonException('generate_gir only accepts a single argument when one of the arguments is an executable') - self.gir_dep, pkgargs = self._get_gir_dep(state) - # find_program is needed in the case g-i is built as subproject. - # In that case it uses override_find_program so the gobject utilities - # can be used from the build dir instead of from the system. - # However, GObject-introspection provides the appropriate paths to - # these utilities via pkg-config, so it would be best to use the - # results from pkg-config when possible. - gi_util_dirs_check = [state.environment.get_build_dir(), state.environment.get_source_dir()] - giscanner = self.interpreter.find_program_impl('g-ir-scanner') - if giscanner.found(): - giscanner_path = giscanner.get_command()[0] - if not any(x in giscanner_path for x in gi_util_dirs_check): - giscanner = self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {}) - else: - giscanner = self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {}) - - gicompiler = self.interpreter.find_program_impl('g-ir-compiler') - if gicompiler.found(): - gicompiler_path = gicompiler.get_command()[0] - if not any(x in gicompiler_path for x in gi_util_dirs_check): - gicompiler = self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {}) - else: - gicompiler = self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {}) + gir_dep, giscanner, gicompiler = self._get_gir_dep(state) - ns = kwargs.pop('namespace') - nsversion = kwargs.pop('nsversion') + ns = kwargs.get('namespace') + if not ns: + raise MesonException('Missing "namespace" keyword argument') + nsversion = kwargs.get('nsversion') + if not nsversion: + raise MesonException('Missing "nsversion" keyword argument') libsources = mesonlib.extract_as_list(kwargs, 'sources', pop=True) girfile = '%s-%s.gir' % (ns, nsversion) srcdir = os.path.join(state.environment.get_source_dir(), state.subdir) builddir = os.path.join(state.environment.get_build_dir(), state.subdir) - depends = [] + girtargets + depends = gir_dep.sources + girtargets gir_inc_dirs = [] langs_compilers = self._get_girtargets_langs_compilers(girtargets) cflags, internal_ldflags, external_ldflags = self._get_langs_compilers_flags(state, langs_compilers) deps = self._get_gir_targets_deps(girtargets) deps += mesonlib.unholder(extract_as_list(kwargs, 'dependencies', pop=True)) + deps += [gir_dep] typelib_includes = self._gather_typelib_includes_and_update_depends(state, deps, depends) # ldflags will be misinterpreted by gir scanner (showing # spurious dependencies) but building GStreamer fails if they @@ -781,7 +770,6 @@ class GnomeModule(ExtensionModule): inc_dirs = self._scan_inc_dirs(kwargs) scan_command = [giscanner] - scan_command += pkgargs scan_command += ['--no-libtool'] scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion] scan_command += ['--warn-all'] @@ -806,7 +794,7 @@ class GnomeModule(ExtensionModule): scan_command += self._scan_langs(state, [lc[0] for lc in langs_compilers]) scan_command += list(external_ldflags) - if gir_has_option(self.interpreter, '--sources-top-dirs'): + if self._gir_has_option('--sources-top-dirs'): scan_command += ['--sources-top-dirs', os.path.join(state.environment.get_source_dir(), self.interpreter.subproject_dir, state.subproject)] scan_command += ['--sources-top-dirs', os.path.join(state.environment.get_build_dir(), self.interpreter.subproject_dir, state.subproject)] @@ -846,6 +834,8 @@ class GnomeModule(ExtensionModule): return ModuleReturnValue(target_g, [target_g]) @permittedKwargs({'sources', 'media', 'symlink_media', 'languages'}) + @FeatureDeprecatedKwargs('gnome.yelp', '0.43.0', ['languages'], + 'Use a LINGUAS file in the source directory instead') def yelp(self, state, args, kwargs): if len(args) < 1: raise MesonException('Yelp requires a project id') @@ -860,11 +850,6 @@ class GnomeModule(ExtensionModule): source_str = '@@'.join(sources) langs = mesonlib.stringlistify(kwargs.pop('languages', [])) - if langs: - mlog.deprecation('''The "languages" argument of gnome.yelp() is deprecated. -Use a LINGUAS file in the sources directory instead. -This will become a hard error in the future.''') - media = mesonlib.stringlistify(kwargs.pop('media', [])) symlinks = kwargs.pop('symlink_media', True) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 666a93d..18baf0c 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -76,7 +76,7 @@ class DependenciesHelper: processed_reqs = [] for obj in mesonlib.unholder(mesonlib.listify(reqs)): if not isinstance(obj, str): - FeatureNew('pkgconfig.generate requirement from non-string object', '0.46.0').use(self.state.subproject) + FeatureNew.single_use('pkgconfig.generate requirement from non-string object', '0.46.0', self.state.subproject) if hasattr(obj, 'generated_pc'): self._check_generated_pc_deprecation(obj) processed_reqs.append(obj.generated_pc) @@ -379,7 +379,7 @@ class PkgConfigModule(ExtensionModule): return cflags_buf cflags = generate_compiler_flags() - ofile.write('Cflags:') + ofile.write('Cflags: ') if uninstalled: ofile.write(' '.join(generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs))) elif not dataonly and cflags: @@ -394,8 +394,6 @@ class PkgConfigModule(ExtensionModule): 'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions', 'dataonly', 'conflicts'}) def generate(self, state, args, kwargs): - if 'variables' in kwargs: - FeatureNew('custom pkgconfig variables', '0.41.0').use(state.subproject) default_version = state.project_version['version'] default_install_dir = None default_description = None @@ -403,9 +401,9 @@ class PkgConfigModule(ExtensionModule): mainlib = None default_subdirs = ['.'] if not args and 'version' not in kwargs: - FeatureNew('pkgconfig.generate implicit version keyword', '0.46.0').use(state.subproject) + FeatureNew.single_use('pkgconfig.generate implicit version keyword', '0.46.0', state.subproject) elif len(args) == 1: - FeatureNew('pkgconfig.generate optional positional argument', '0.46.0').use(state.subproject) + FeatureNew.single_use('pkgconfig.generate optional positional argument', '0.46.0', state.subproject) mainlib = getattr(args[0], 'held_object', args[0]) if not isinstance(mainlib, (build.StaticLibrary, build.SharedLibrary)): raise mesonlib.MesonException('Pkgconfig_gen first positional argument must be a library object') diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index a5c58a2..ceabd76 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -285,7 +285,7 @@ print (json.dumps ({ class PythonInstallation(ExternalProgramHolder): def __init__(self, interpreter, python, info): - ExternalProgramHolder.__init__(self, python) + ExternalProgramHolder.__init__(self, python, interpreter.subproject) self.interpreter = interpreter self.subproject = self.interpreter.subproject prefix = self.interpreter.environment.coredata.get_builtin_option('prefix') @@ -361,7 +361,7 @@ class PythonInstallation(ExternalProgramHolder): @permittedKwargs(['pure', 'subdir']) def install_sources_method(self, args, kwargs): - pure = kwargs.pop('pure', False) + pure = kwargs.pop('pure', True) if not isinstance(pure, bool): raise InvalidArguments('"pure" argument must be a boolean.') @@ -514,7 +514,7 @@ class PythonModule(ExtensionModule): if disabled: mlog.log('Program', name_or_path or 'python', 'found:', mlog.red('NO'), '(disabled by:', mlog.bold(feature), ')') - return ExternalProgramHolder(NonExistingExternalProgram()) + return ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) if not name_or_path: python = ExternalProgram('python3', mesonlib.python_command, silent=True) @@ -561,11 +561,11 @@ class PythonModule(ExtensionModule): if not python.found(): if required: raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python')) - res = ExternalProgramHolder(NonExistingExternalProgram()) + res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) elif missing_modules: if required: raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules))) - res = ExternalProgramHolder(NonExistingExternalProgram()) + res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) else: # Sanity check, we expect to have something that at least quacks in tune try: @@ -583,7 +583,7 @@ class PythonModule(ExtensionModule): if isinstance(info, dict) and 'version' in info and self._check_version(name_or_path, info['version']): res = PythonInstallation(interpreter, python, info) else: - res = ExternalProgramHolder(NonExistingExternalProgram()) + res = ExternalProgramHolder(NonExistingExternalProgram(), state.subproject) if required: raise mesonlib.MesonException('{} is not a valid python or it is missing setuptools'.format(python)) diff --git a/mesonbuild/modules/unstable_kconfig.py b/mesonbuild/modules/unstable_keyval.py index 6685710..3da2992 100644 --- a/mesonbuild/modules/unstable_kconfig.py +++ b/mesonbuild/modules/unstable_keyval.py @@ -21,9 +21,9 @@ from ..interpreter import InvalidCode import os -class KconfigModule(ExtensionModule): +class KeyvalModule(ExtensionModule): - @FeatureNew('Kconfig Module', '0.51.0') + @FeatureNew('Keyval Module', '0.55.0') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.snippets.add('load') @@ -56,9 +56,7 @@ class KconfigModule(ExtensionModule): s = sources[0] is_built = False if isinstance(s, mesonlib.File): - if s.is_built: - FeatureNew('kconfig.load() of built files', '0.52.0').use(state.subproject) - is_built = True + is_built = is_built or s.is_built s = s.absolute_path(interpreter.environment.source_dir, interpreter.environment.build_dir) else: s = os.path.join(interpreter.environment.source_dir, s) @@ -70,4 +68,4 @@ class KconfigModule(ExtensionModule): def initialize(*args, **kwargs): - return KconfigModule(*args, **kwargs) + return KeyvalModule(*args, **kwargs) diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 2cffc47..b9e381e 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -426,8 +426,8 @@ class IfNode(BaseNode): class IfClauseNode(BaseNode): def __init__(self, linenode: BaseNode): super().__init__(linenode.lineno, linenode.colno, linenode.filename) - self.ifs = [] # type: T.List[IfNode] - self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename) # type: T.Union[EmptyNode, CodeBlockNode] + self.ifs = [] # type: T.List[IfNode] + self.elseblock = None # type: T.Union[EmptyNode, CodeBlockNode] class UMinusNode(BaseNode): def __init__(self, current_location: Token, value: BaseNode): @@ -747,9 +747,7 @@ class Parser: block = self.codeblock() clause.ifs.append(IfNode(clause, condition, block)) self.elseifblock(clause) - elseblock = self.elseblock() - if elseblock: - clause.elseblock = elseblock + clause.elseblock = self.elseblock() return clause def elseifblock(self, clause) -> None: @@ -759,11 +757,11 @@ class Parser: b = self.codeblock() clause.ifs.append(IfNode(s, s, b)) - def elseblock(self) -> T.Optional[CodeBlockNode]: + def elseblock(self) -> T.Union[CodeBlockNode, EmptyNode]: if self.accept('else'): self.expect('eol') return self.codeblock() - return None + return EmptyNode(self.current.lineno, self.current.colno, self.current.filename) def line(self) -> BaseNode: block_start = self.current diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index 77d8377..2521511 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -86,7 +86,7 @@ class MesonApp: # will cause a crash for l in os.listdir(self.build_dir): l = os.path.join(self.build_dir, l) - if os.path.isdir(l): + if os.path.isdir(l) and not os.path.islink(l): mesonlib.windows_proof_rmtree(l) else: mesonlib.windows_proof_rm(l) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 23643c5..4aafe62 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -33,14 +33,17 @@ import signal import subprocess import sys import tempfile +import textwrap import time import typing as T +import xml.etree.ElementTree as et from . import build from . import environment from . import mlog from .dependencies import ExternalProgram -from .mesonlib import MesonException, get_wine_shortpath, split_args +from .mesonlib import MesonException, get_wine_shortpath, split_args, join_args +from .backend.backends import TestProtocol if T.TYPE_CHECKING: from .backend.backends import TestSerialisation @@ -92,6 +95,9 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args, help='wrapper to run tests with (e.g. Valgrind)') parser.add_argument('-C', default='.', dest='wd', + # https://github.com/python/typeshed/issues/3107 + # https://github.com/python/mypy/issues/7177 + type=os.path.abspath, # type: ignore help='directory to cd into before running') parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE', help='Only run tests belonging to the given suite.') @@ -305,7 +311,7 @@ class TAPParser: yield self.Version(version=version) continue - if len(line) == 0: + if not line: continue yield self.Error('unexpected input at line {}'.format((lineno,))) @@ -320,13 +326,144 @@ class TAPParser: yield self.Error('Too many tests run (expected {}, got {})'.format(plan.count, num_tests)) + +class JunitBuilder: + + """Builder for Junit test results. + + Junit is impossible to stream out, it requires attributes counting the + total number of tests, failures, skips, and errors in the root element + and in each test suite. As such, we use a builder class to track each + test case, and calculate all metadata before writing it out. + + For tests with multiple results (like from a TAP test), we record the + test as a suite with the project_name.test_name. This allows us to track + each result separately. For tests with only one result (such as exit-code + tests) we record each one into a suite with the name project_name. The use + of the project_name allows us to sort subproject tests separately from + the root project. + """ + + def __init__(self, filename: str) -> None: + self.filename = filename + self.root = et.Element( + 'testsuites', tests='0', errors='0', failures='0') + self.suites = {} # type: T.Dict[str, et.Element] + + def log(self, name: str, test: 'TestRun') -> None: + """Log a single test case.""" + if test.junit is not None: + for suite in test.junit.findall('.//testsuite'): + # Assume that we don't need to merge anything here... + suite.attrib['name'] = '{}.{}.{}'.format(test.project, name, suite.attrib['name']) + + # GTest can inject invalid attributes + for case in suite.findall('.//testcase[@result]'): + del case.attrib['result'] + for case in suite.findall('.//testcase[@timestamp]'): + del case.attrib['timestamp'] + self.root.append(suite) + return + + # In this case we have a test binary with multiple results. + # We want to record this so that each result is recorded + # separately + if test.results: + suitename = '{}.{}'.format(test.project, name) + assert suitename not in self.suites, 'duplicate suite' + + suite = self.suites[suitename] = et.Element( + 'testsuite', + name=suitename, + tests=str(len(test.results)), + errors=str(sum(1 for r in test.results if r is TestResult.ERROR)), + failures=str(sum(1 for r in test.results if r in + {TestResult.FAIL, TestResult.UNEXPECTEDPASS, TestResult.TIMEOUT})), + skipped=str(sum(1 for r in test.results if r is TestResult.SKIP)), + ) + + for i, result in enumerate(test.results): + # Both name and classname are required. Set them both to the + # number of the test in a TAP test, as TAP doesn't give names. + testcase = et.SubElement(suite, 'testcase', name=str(i), classname=str(i)) + if result is TestResult.SKIP: + et.SubElement(testcase, 'skipped') + elif result is TestResult.ERROR: + et.SubElement(testcase, 'error') + elif result is TestResult.FAIL: + et.SubElement(testcase, 'failure') + elif result is TestResult.UNEXPECTEDPASS: + fail = et.SubElement(testcase, 'failure') + fail.text = 'Test unexpected passed.' + elif result is TestResult.TIMEOUT: + fail = et.SubElement(testcase, 'failure') + fail.text = 'Test did not finish before configured timeout.' + if test.stdo: + out = et.SubElement(suite, 'system-out') + out.text = test.stdo.rstrip() + if test.stde: + err = et.SubElement(suite, 'system-err') + err.text = test.stde.rstrip() + else: + if test.project not in self.suites: + suite = self.suites[test.project] = et.Element( + 'testsuite', name=test.project, tests='1', errors='0', + failures='0', skipped='0') + else: + suite = self.suites[test.project] + suite.attrib['tests'] = str(int(suite.attrib['tests']) + 1) + + testcase = et.SubElement(suite, 'testcase', name=name, classname=name) + if test.res is TestResult.SKIP: + et.SubElement(testcase, 'skipped') + suite.attrib['skipped'] = str(int(suite.attrib['skipped']) + 1) + elif test.res is TestResult.ERROR: + et.SubElement(testcase, 'error') + suite.attrib['errors'] = str(int(suite.attrib['errors']) + 1) + elif test.res is TestResult.FAIL: + et.SubElement(testcase, 'failure') + suite.attrib['failures'] = str(int(suite.attrib['failures']) + 1) + if test.stdo: + out = et.SubElement(testcase, 'system-out') + out.text = test.stdo.rstrip() + if test.stde: + err = et.SubElement(testcase, 'system-err') + err.text = test.stde.rstrip() + + def write(self) -> None: + """Calculate total test counts and write out the xml result.""" + for suite in self.suites.values(): + self.root.append(suite) + # Skipped is really not allowed in the "testsuits" element + for attr in ['tests', 'errors', 'failures']: + self.root.attrib[attr] = str(int(self.root.attrib[attr]) + int(suite.attrib[attr])) + + tree = et.ElementTree(self.root) + with open(self.filename, 'wb') as f: + tree.write(f, encoding='utf-8', xml_declaration=True) + + class TestRun: @classmethod + def make_gtest(cls, test: 'TestSerialisation', test_env: T.Dict[str, str], + returncode: int, starttime: float, duration: float, + stdo: T.Optional[str], stde: T.Optional[str], + cmd: T.Optional[T.List[str]]) -> 'TestRun': + filename = '{}.xml'.format(test.name) + if test.workdir: + filename = os.path.join(test.workdir, filename) + tree = et.parse(filename) + + return cls.make_exitcode( + test, test_env, returncode, starttime, duration, stdo, stde, cmd, + junit=tree) + + @classmethod def make_exitcode(cls, test: 'TestSerialisation', test_env: T.Dict[str, str], returncode: int, starttime: float, duration: float, stdo: T.Optional[str], stde: T.Optional[str], - cmd: T.Optional[T.List[str]]) -> 'TestRun': + cmd: T.Optional[T.List[str]], **kwargs) -> 'TestRun': if returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP elif returncode == GNU_ERROR_RETURNCODE: @@ -335,30 +472,29 @@ class TestRun: res = TestResult.EXPECTEDFAIL if bool(returncode) else TestResult.UNEXPECTEDPASS else: res = TestResult.FAIL if bool(returncode) else TestResult.OK - return cls(test, test_env, res, returncode, starttime, duration, stdo, stde, cmd) + return cls(test, test_env, res, [], returncode, starttime, duration, stdo, stde, cmd, **kwargs) @classmethod def make_tap(cls, test: 'TestSerialisation', test_env: T.Dict[str, str], returncode: int, starttime: float, duration: float, stdo: str, stde: str, cmd: T.Optional[T.List[str]]) -> 'TestRun': - res = None - num_tests = 0 + res = None # type: T.Optional[TestResult] + results = [] # type: T.List[TestResult] failed = False - num_skipped = 0 for i in TAPParser(io.StringIO(stdo)).parse(): if isinstance(i, TAPParser.Bailout): - res = TestResult.ERROR + results.append(TestResult.ERROR) + failed = True elif isinstance(i, TAPParser.Test): - if i.result == TestResult.SKIP: - num_skipped += 1 - elif i.result in (TestResult.FAIL, TestResult.UNEXPECTEDPASS): + results.append(i.result) + if i.result not in {TestResult.OK, TestResult.EXPECTEDFAIL}: failed = True - num_tests += 1 elif isinstance(i, TAPParser.Error): - res = TestResult.ERROR + results.append(TestResult.ERROR) stde += '\nTAP parsing error: ' + i.message + failed = True if returncode != 0: res = TestResult.ERROR @@ -366,7 +502,7 @@ class TestRun: if res is None: # Now determine the overall result of the test based on the outcome of the subcases - if num_skipped == num_tests: + if all(t is TestResult.SKIP for t in results): # This includes the case where num_tests is zero res = TestResult.SKIP elif test.should_fail: @@ -374,14 +510,16 @@ class TestRun: else: res = TestResult.FAIL if failed else TestResult.OK - return cls(test, test_env, res, returncode, starttime, duration, stdo, stde, cmd) + return cls(test, test_env, res, results, returncode, starttime, duration, stdo, stde, cmd) def __init__(self, test: 'TestSerialisation', test_env: T.Dict[str, str], - res: TestResult, returncode: int, starttime: float, duration: float, + res: TestResult, results: T.List[TestResult], returncode: + int, starttime: float, duration: float, stdo: T.Optional[str], stde: T.Optional[str], - cmd: T.Optional[T.List[str]]): + cmd: T.Optional[T.List[str]], *, junit: T.Optional[et.ElementTree] = None): assert isinstance(res, TestResult) self.res = res + self.results = results # May be an empty list self.returncode = returncode self.starttime = starttime self.duration = duration @@ -390,6 +528,8 @@ class TestRun: self.cmd = cmd self.env = test_env self.should_fail = test.should_fail + self.project = test.project_name + self.junit = junit def get_log(self) -> str: res = '--- command ---\n' @@ -436,9 +576,7 @@ def write_json_log(jsonlogfile: T.TextIO, test_name: str, result: TestRun) -> No jsonlogfile.write(json.dumps(jresult) + '\n') def run_with_mono(fname: str) -> bool: - if fname.endswith('.exe') and not (is_windows() or is_cygwin()): - return True - return False + return fname.endswith('.exe') and not (is_windows() or is_cygwin()) def load_benchmarks(build_dir: str) -> T.List['TestSerialisation']: datafile = Path(build_dir) / 'meson-private' / 'meson_benchmark_setup.dat' @@ -471,26 +609,26 @@ class SingleTestRunner: return ['java', '-jar'] + self.test.fname elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]): return ['mono'] + self.test.fname - else: - if self.test.is_cross_built and self.test.needs_exe_wrapper: - if self.test.exe_runner is None: - # Can not run test on cross compiled executable - # because there is no execute wrapper. - return None - else: - if not self.test.exe_runner.found(): - msg = 'The exe_wrapper defined in the cross file {!r} was not ' \ - 'found. Please check the command and/or add it to PATH.' - raise TestException(msg.format(self.test.exe_runner.name)) - return self.test.exe_runner.get_command() + self.test.fname - else: - return self.test.fname + elif self.test.cmd_is_built and self.test.needs_exe_wrapper: + if self.test.exe_runner is None: + # Can not run test on cross compiled executable + # because there is no execute wrapper. + return None + elif self.test.cmd_is_built: + # If the command is not built (ie, its a python script), + # then we don't check for the exe-wrapper + if not self.test.exe_runner.found(): + msg = ('The exe_wrapper defined in the cross file {!r} was not ' + 'found. Please check the command and/or add it to PATH.') + raise TestException(msg.format(self.test.exe_runner.name)) + return self.test.exe_runner.get_command() + self.test.fname + return self.test.fname def run(self) -> TestRun: cmd = self._get_cmd() if cmd is None: skip_stdout = 'Not run because can not execute cross compiled binaries.' - return TestRun(self.test, self.test_env, TestResult.SKIP, GNU_SKIP_RETURNCODE, time.time(), 0.0, skip_stdout, None, None) + return TestRun(self.test, self.test_env, TestResult.SKIP, [], GNU_SKIP_RETURNCODE, time.time(), 0.0, skip_stdout, None, None) else: wrap = TestHarness.get_wrapper(self.options) if self.options.gdb: @@ -500,7 +638,7 @@ class SingleTestRunner: def _run_cmd(self, cmd: T.List[str]) -> TestRun: starttime = time.time() - if len(self.test.extra_paths) > 0: + if self.test.extra_paths: self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH'] winecmd = [] for c in cmd: @@ -525,7 +663,7 @@ class SingleTestRunner: if not self.options.verbose: stdout = tempfile.TemporaryFile("wb+") stderr = tempfile.TemporaryFile("wb+") if self.options.split else stdout - if self.test.protocol == 'tap' and stderr is stdout: + if self.test.protocol is TestProtocol.TAP and stderr is stdout: stdout = tempfile.TemporaryFile("wb+") # Let gdb handle ^C instead of us @@ -545,7 +683,14 @@ class SingleTestRunner: # errors avoid not being able to use the terminal. os.setsid() # type: ignore - p = subprocess.Popen(cmd, + extra_cmd = [] # type: T.List[str] + if self.test.protocol is TestProtocol.GTEST: + gtestname = '{}.xml'.format(self.test.name) + if self.test.workdir: + gtestname = '{}:{}'.format(self.test.workdir, self.test.name) + extra_cmd.append('--gtest_output=xml:{}'.format(gtestname)) + + p = subprocess.Popen(cmd + extra_cmd, stdout=stdout, stderr=stderr, env=self.env, @@ -633,10 +778,12 @@ class SingleTestRunner: stdo = "" stde = additional_error if timed_out: - return TestRun(self.test, self.test_env, TestResult.TIMEOUT, p.returncode, starttime, duration, stdo, stde, cmd) + return TestRun(self.test, self.test_env, TestResult.TIMEOUT, [], p.returncode, starttime, duration, stdo, stde, cmd) else: - if self.test.protocol == 'exitcode': + if self.test.protocol is TestProtocol.EXITCODE: return TestRun.make_exitcode(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd) + elif self.test.protocol is TestProtocol.GTEST: + return TestRun.make_gtest(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd) else: if self.options.verbose: print(stdo, end='') @@ -655,9 +802,11 @@ class TestHarness: self.timeout_count = 0 self.is_run = False self.tests = None + self.results = [] # type: T.List[TestRun] self.logfilename = None # type: T.Optional[str] self.logfile = None # type: T.Optional[T.TextIO] self.jsonlogfile = None # type: T.Optional[T.TextIO] + self.junit = None # type: T.Optional[JunitBuilder] if self.options.benchmark: self.tests = load_benchmarks(options.wd) else: @@ -678,12 +827,11 @@ class TestHarness: self.close_logfiles() def close_logfiles(self) -> None: - if self.logfile: - self.logfile.close() - self.logfile = None - if self.jsonlogfile: - self.jsonlogfile.close() - self.jsonlogfile = None + for f in ['logfile', 'jsonlogfile']: + lfile = getattr(self, f) + if lfile: + lfile.close() + setattr(self, f, None) def merge_suite_options(self, options: argparse.Namespace, test: 'TestSerialisation') -> T.Dict[str, str]: if ':' in options.setup: @@ -719,6 +867,9 @@ class TestHarness: env = os.environ.copy() test_env = test.env.get_env(env) env.update(test_env) + if (test.is_cross_built and test.needs_exe_wrapper and + test.exe_runner and test.exe_runner.found()): + env['MESON_EXE_WRAPPER'] = join_args(test.exe_runner.get_command()) return SingleTestRunner(test, test_env, env, options) def process_test_result(self, result: TestRun) -> None: @@ -773,23 +924,27 @@ class TestHarness: self.logfile.write(result_str) if self.jsonlogfile: write_json_log(self.jsonlogfile, name, result) + if self.junit: + self.junit.log(name, result) def print_summary(self) -> None: - msg = ''' -Ok: {:<4} -Expected Fail: {:<4} -Fail: {:<4} -Unexpected Pass: {:<4} -Skipped: {:<4} -Timeout: {:<4} -'''.format(self.success_count, self.expectedfail_count, self.fail_count, + msg = textwrap.dedent(''' + Ok: {:<4} + Expected Fail: {:<4} + Fail: {:<4} + Unexpected Pass: {:<4} + Skipped: {:<4} + Timeout: {:<4} + ''').format(self.success_count, self.expectedfail_count, self.fail_count, self.unexpectedpass_count, self.skip_count, self.timeout_count) print(msg) if self.logfile: self.logfile.write(msg) + if self.junit: + self.junit.write() def print_collected_logs(self) -> None: - if len(self.collected_logs) > 0: + if self.collected_logs: if len(self.collected_logs) > 10: print('\nThe output from 10 first failed tests:\n') else: @@ -871,7 +1026,7 @@ Timeout: {:<4} print('No tests defined.') return [] - if len(self.options.include_suites) or len(self.options.exclude_suites): + if self.options.include_suites or self.options.exclude_suites: tests = [] for tst in self.tests: if self.test_suitable(tst): @@ -903,6 +1058,9 @@ Timeout: {:<4} if namebase: logfile_base += '-' + namebase.replace(' ', '_') + + self.junit = JunitBuilder(logfile_base + '.junit.xml') + self.logfilename = logfile_base + '.txt' self.jsonlogfilename = logfile_base + '.json' @@ -930,7 +1088,7 @@ Timeout: {:<4} if len(self.suites) > 1 and test.suite: rv = TestHarness.split_suite_string(test.suite[0])[0] s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite) - if len(s): + if s: rv += ":" return rv + s + " / " + test.name else: @@ -1046,7 +1204,6 @@ def run(options: argparse.Namespace) -> int: if not exe.found(): print('Could not find requested program: {!r}'.format(check_bin)) return 1 - options.wd = os.path.abspath(options.wd) if not options.list and not options.no_rebuild: if not rebuild_all(options.wd): diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index c13cc5d..d47a3d2 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -16,10 +16,11 @@ import re import functools import typing as T -from . import mparser +from . import compilers from . import coredata from . import mesonlib -from . import compilers +from . import mparser +from .interpreterbase import FeatureNew forbidden_option_names = set(coredata.builtin_options.keys()) forbidden_prefixes = [lang + '_' for lang in compilers.all_languages] + ['b_', 'backend_'] @@ -170,12 +171,21 @@ class OptionInterpreter: res = self.reduce_single(arg.value) if not isinstance(res, (int, float)): raise OptionException('Token after "-" is not a number') + FeatureNew.single_use('negative numbers in meson_options.txt', '0.54.1', self.subproject) return -res elif isinstance(arg, mparser.NotNode): res = self.reduce_single(arg.value) if not isinstance(res, bool): raise OptionException('Token after "not" is not a a boolean') + FeatureNew.single_use('negation ("not") in meson_options.txt', '0.54.1', self.subproject) return not res + elif isinstance(arg, mparser.ArithmeticNode): + l = self.reduce_single(arg.left) + r = self.reduce_single(arg.right) + if not (arg.operation == 'add' and isinstance(l, str) and isinstance(r, str)): + raise OptionException('Only string concatenation with the "+" operator is allowed') + FeatureNew.single_use('string concatenation in meson_options.txt', '0.55.0', self.subproject) + return l + r else: raise OptionException('Arguments may only be string, int, bool, or array of those.') @@ -200,11 +210,8 @@ class OptionInterpreter: raise OptionException('Only calls to option() are allowed in option files.') (posargs, kwargs) = self.reduce_arguments(node.args) - # FIXME: Cannot use FeatureNew while parsing options because we parse - # it before reading options in project(). See func_project() in - # interpreter.py - #if 'yield' in kwargs: - # FeatureNew('option yield', '0.45.0').use(self.subproject) + if 'yield' in kwargs: + FeatureNew.single_use('option yield', '0.45.0', self.subproject) if 'type' not in kwargs: raise OptionException('Option call missing mandatory "type" keyword argument') diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 5ba3a97..a3a3eff 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -290,13 +290,13 @@ class Elf(DataSizes): self.bf.seek(offset) self.bf.write(newname) - def fix_rpath(self, new_rpath): + def fix_rpath(self, rpath_dirs_to_remove, new_rpath): # The path to search for can be either rpath or runpath. # Fix both of them to be sure. - self.fix_rpathtype_entry(new_rpath, DT_RPATH) - self.fix_rpathtype_entry(new_rpath, DT_RUNPATH) + self.fix_rpathtype_entry(rpath_dirs_to_remove, new_rpath, DT_RPATH) + self.fix_rpathtype_entry(rpath_dirs_to_remove, new_rpath, DT_RUNPATH) - def fix_rpathtype_entry(self, new_rpath, entrynum): + def fix_rpathtype_entry(self, rpath_dirs_to_remove, new_rpath, entrynum): if isinstance(new_rpath, str): new_rpath = new_rpath.encode('utf8') rp_off = self.get_entry_offset(entrynum) @@ -305,7 +305,23 @@ class Elf(DataSizes): print('File does not have rpath. It should be a fully static executable.') return self.bf.seek(rp_off) + old_rpath = self.read_str() + new_rpaths = [] + if new_rpath: + new_rpaths.append(new_rpath) + if old_rpath: + # Filter out build-only rpath entries + # added by get_link_dep_subdirs() or + # specified by user with build_rpath. + for dir in old_rpath.split(b':'): + if not (dir in rpath_dirs_to_remove or + dir == (b'X' * len(dir))): + new_rpaths.append(dir) + + # Prepend user-specified new entries while preserving the ones that came from pkgconfig etc. + new_rpath = b':'.join(new_rpaths) + if len(old_rpath) < len(new_rpath): sys.exit("New rpath must not be longer than the old one.") # The linker does read-only string deduplication. If there is a @@ -343,13 +359,13 @@ class Elf(DataSizes): entry.write(self.bf) return None -def fix_elf(fname, new_rpath, verbose=True): +def fix_elf(fname, rpath_dirs_to_remove, new_rpath, verbose=True): with Elf(fname, verbose) as e: if new_rpath is None: e.print_rpath() e.print_runpath() else: - e.fix_rpath(new_rpath) + e.fix_rpath(rpath_dirs_to_remove, new_rpath) def get_darwin_rpaths_to_remove(fname): out = subprocess.check_output(['otool', '-l', fname], @@ -430,7 +446,7 @@ def fix_jar(fname): f.truncate() subprocess.check_call(['jar', 'ufm', fname, 'META-INF/MANIFEST.MF']) -def fix_rpath(fname, new_rpath, final_path, install_name_mappings, verbose=True): +def fix_rpath(fname, rpath_dirs_to_remove, new_rpath, final_path, install_name_mappings, verbose=True): global INSTALL_NAME_TOOL # Static libraries, import libraries, debug information, headers, etc # never have rpaths @@ -441,7 +457,7 @@ def fix_rpath(fname, new_rpath, final_path, install_name_mappings, verbose=True) if fname.endswith('.jar'): fix_jar(fname) return - fix_elf(fname, new_rpath, verbose) + fix_elf(fname, rpath_dirs_to_remove, new_rpath, verbose) return except SystemExit as e: if isinstance(e.code, int) and e.code == 0: diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index d393f93..41cca26 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -113,7 +113,10 @@ def gnu_syms(libfilename: str, outfilename: str): continue line_split = line.split() entry = line_split[0:2] - if len(line_split) >= 4: + # Store the size of symbols pointing to data objects so we relink + # when those change, which is needed because of copy relocations + # https://github.com/mesonbuild/meson/pull/7132#issuecomment-628353702 + if line_split[1].upper() in ('B', 'G', 'D') and len(line_split) >= 4: entry += [line_split[3]] result += [' '.join(entry)] write_if_changed('\n'.join(result) + '\n', outfilename) @@ -139,6 +142,23 @@ def osx_syms(libfilename: str, outfilename: str): result += [' '.join(x.split()[0:2]) for x in output.split('\n')] write_if_changed('\n'.join(result) + '\n', outfilename) +def openbsd_syms(libfilename: str, outfilename: str): + # Get the name of the library + output = call_tool('readelf', ['-d', libfilename]) + if not output: + dummy_syms(outfilename) + return + result = [x for x in output.split('\n') if 'SONAME' in x] + assert(len(result) <= 1) + # Get a list of all symbols exported + output = call_tool('nm', ['-D', '-P', '-g', libfilename]) + if not output: + dummy_syms(outfilename) + return + # U = undefined (cope with the lack of --defined-only option) + result += [' '.join(x.split()[0:2]) for x in output.split('\n') if x and not x.endswith('U ')] + write_if_changed('\n'.join(result) + '\n', outfilename) + def cygwin_syms(impfilename: str, outfilename: str): # Get the name of the library output = call_tool('dlltool', ['-I', impfilename]) @@ -234,6 +254,8 @@ def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host gnu_syms(libfilename, outfilename) elif mesonlib.is_osx(): osx_syms(libfilename, outfilename) + elif mesonlib.is_openbsd(): + openbsd_syms(libfilename, outfilename) elif mesonlib.is_windows(): if os.path.isfile(impfilename): windows_syms(impfilename, outfilename) diff --git a/msi/createmsi.py b/msi/createmsi.py index f80d1dc..4d03593 100644 --- a/msi/createmsi.py +++ b/msi/createmsi.py @@ -153,6 +153,7 @@ class PackageGenerator: if os.path.exists(sdir): shutil.rmtree(sdir) main_stage, ninja_stage = self.staging_dirs + dep_data_dir = 'mesonbuild/dependencies/data' modules = self.get_all_modules_from_dir('mesonbuild/modules') modules += self.get_all_modules_from_dir('mesonbuild/scripts') modules += self.get_more_modules() @@ -174,6 +175,7 @@ class PackageGenerator: pyinst_cmd += ['meson.py'] subprocess.check_call(pyinst_cmd) shutil.move(pyinstaller_tmpdir + '/meson', main_stage) + shutil.copytree(dep_data_dir, main_stage + '/mesonbuild/dependencies/data') if not os.path.exists(os.path.join(main_stage, 'meson.exe')): sys.exit('Meson exe missing from staging dir.') os.mkdir(ninja_stage) diff --git a/run_project_tests.py b/run_project_tests.py index 875a522..5fc8aa2 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -50,7 +50,7 @@ from run_tests import ensure_backend_detects_changes from run_tests import guess_backend ALL_TESTS = ['cmake', 'common', 'warning-meson', 'failing-meson', 'failing-build', 'failing-test', - 'kconfig', 'platform-osx', 'platform-windows', 'platform-linux', + 'keyval', 'platform-osx', 'platform-windows', 'platform-linux', 'java', 'C#', 'vala', 'rust', 'd', 'objective c', 'objective c++', 'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm' ] @@ -190,7 +190,8 @@ class TestDef: self.skip = skip self.env = os.environ.copy() self.installed_files = [] # type: T.List[InstalledFile] - self.do_not_set_opts = [] # type: T.List[str] + self.do_not_set_opts = [] # type: T.List[str] + self.stdout = [] # type: T.List[T.Dict[str, str]] def __repr__(self) -> str: return '<{}: {:<48} [{}: {}] -- {}>'.format(type(self).__name__, str(self.path), self.name, self.args, self.skip) @@ -233,6 +234,7 @@ no_meson_log_msg = 'No meson-log.txt found.' system_compiler = None compiler_id_map = {} # type: T.Dict[str, str] +tool_vers_map = {} # type: T.Dict[str, str] class StopException(Exception): def __init__(self): @@ -340,19 +342,19 @@ def log_text_file(logfile, testdir, stdo, stde): def bold(text): - return mlog.bold(text).get_text(mlog.colorize_console) + return mlog.bold(text).get_text(mlog.colorize_console()) def green(text): - return mlog.green(text).get_text(mlog.colorize_console) + return mlog.green(text).get_text(mlog.colorize_console()) def red(text): - return mlog.red(text).get_text(mlog.colorize_console) + return mlog.red(text).get_text(mlog.colorize_console()) def yellow(text): - return mlog.yellow(text).get_text(mlog.colorize_console) + return mlog.yellow(text).get_text(mlog.colorize_console()) def _run_ci_include(args: T.List[str]) -> str: @@ -380,6 +382,63 @@ def run_ci_commands(raw_log: str) -> T.List[str]: res += ['CI COMMAND {}:\n{}\n'.format(cmd[0], ci_commands[cmd[0]](cmd[1:]))] return res +def _compare_output(expected: T.List[T.Dict[str, str]], output: str, desc: str) -> str: + if expected: + i = iter(expected) + + def next_expected(i): + # Get the next expected line + item = next(i) + how = item.get('match', 'literal') + expected = item.get('line') + + # Simple heuristic to automatically convert path separators for + # Windows: + # + # Any '/' appearing before 'WARNING' or 'ERROR' (i.e. a path in a + # filename part of a location) is replaced with '\' (in a re: '\\' + # which matches a literal '\') + # + # (There should probably be a way to turn this off for more complex + # cases which don't fit this) + if mesonlib.is_windows(): + if how != "re": + sub = r'\\' + else: + sub = r'\\\\' + expected = re.sub(r'/(?=.*(WARNING|ERROR))', sub, expected) + + return how, expected + + try: + how, expected = next_expected(i) + for actual in output.splitlines(): + if how == "re": + match = bool(re.match(expected, actual)) + else: + match = (expected == actual) + if match: + how, expected = next_expected(i) + + # reached the end of output without finding expected + return 'expected "{}" not found in {}'.format(expected, desc) + except StopIteration: + # matched all expected lines + pass + + return '' + +def validate_output(test: TestDef, stdo: str, stde: str) -> str: + return _compare_output(test.stdout, stdo, 'stdout') + +# There are some class variables and such that cahce +# information. Clear all of these. The better solution +# would be to change the code so that no state is persisted +# but that would be a lot of work given that Meson was originally +# coded to run as a batch process. +def clear_internal_caches(): + import mesonbuild.interpreterbase + mesonbuild.interpreterbase.FeatureNew.feature_registry = {} def run_test_inprocess(testdir): old_stdout = sys.stdout @@ -451,6 +510,11 @@ def _run_test(test: TestDef, test_build_dir: str, install_dir: str, extra_args, cicmds = run_ci_commands(mesonlog) testresult = TestResult(cicmds) testresult.add_step(BuildStep.configure, stdo, stde, mesonlog, time.time() - gen_start) + output_msg = validate_output(test, stdo, stde) + testresult.mlog += output_msg + if output_msg: + testresult.fail('Unexpected output while configuring.') + return testresult if should_fail == 'meson': if returncode == 1: return testresult @@ -496,6 +560,7 @@ def _run_test(test: TestDef, test_build_dir: str, install_dir: str, extra_args, force_regenerate() # Test in-process + clear_internal_caches() test_start = time.time() (returncode, tstdo, tstde, test_log) = run_test_inprocess(test_build_dir) testresult.add_step(BuildStep.test, tstdo, tstde, test_log, time.time() - test_start) @@ -538,18 +603,16 @@ def _run_test(test: TestDef, test_build_dir: str, install_dir: str, extra_args, return testresult -def gather_tests(testdir: Path) -> T.List[TestDef]: +def gather_tests(testdir: Path, stdout_mandatory: bool) -> T.List[TestDef]: tests = [t.name for t in testdir.iterdir() if t.is_dir()] tests = [t for t in tests if not t.startswith('.')] # Filter non-tests files (dot files, etc) test_defs = [TestDef(testdir / t, None, []) for t in tests] all_tests = [] # type: T.List[TestDef] for t in test_defs: + test_def = {} test_def_file = t.path / 'test.json' - if not test_def_file.is_file(): - all_tests += [t] - continue - - test_def = json.loads(test_def_file.read_text()) + if test_def_file.is_file(): + test_def = json.loads(test_def_file.read_text()) # Handle additional environment variables env = {} # type: T.Dict[str, str] @@ -565,14 +628,29 @@ def gather_tests(testdir: Path) -> T.List[TestDef]: if 'installed' in test_def: installed = [InstalledFile(x) for x in test_def['installed']] + # Handle expected output + stdout = test_def.get('stdout', []) + if stdout_mandatory and not stdout: + raise RuntimeError("{} must contain a non-empty stdout key".format(test_def_file)) + # Handle the do_not_set_opts list do_not_set_opts = test_def.get('do_not_set_opts', []) # type: T.List[str] + # Skip tests if the tool requirements are not met + if 'tools' in test_def: + assert isinstance(test_def['tools'], dict) + for tool, vers_req in test_def['tools'].items(): + if tool not in tool_vers_map: + t.skip = True + elif not mesonlib.version_compare(tool_vers_map[tool], vers_req): + t.skip = True + # Skip the matrix code and just update the existing test if 'matrix' not in test_def: t.env.update(env) t.installed_files = installed t.do_not_set_opts = do_not_set_opts + t.stdout = stdout all_tests += [t] continue @@ -639,10 +717,11 @@ def gather_tests(testdir: Path) -> T.List[TestDef]: name = ' '.join([x[0] for x in i if x[0] is not None]) opts = ['-D' + x[0] for x in i if x[0] is not None] skip = any([x[1] for x in i]) - test = TestDef(t.path, name, opts, skip) + test = TestDef(t.path, name, opts, skip or t.skip) test.env.update(env) test.installed_files = installed test.do_not_set_opts = do_not_set_opts + test.stdout = stdout all_tests += [test] return sorted(all_tests) @@ -827,45 +906,50 @@ def detect_tests_to_run(only: T.List[str], use_tmp: bool) -> T.List[T.Tuple[str, shutil.which('pgfortran') or shutil.which('ifort')) - # Name, subdirectory, skip condition. + class TestCategory: + def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandatory: bool = False): + self.category = category # category name + self.subdir = subdir # subdirectory + self.skip = skip # skip condition + self.stdout_mandatory = stdout_mandatory # expected stdout is mandatory for tests in this categroy + all_tests = [ - ('cmake', 'cmake', not shutil.which('cmake') or (os.environ.get('compiler') == 'msvc2015' and under_ci)), - ('common', 'common', False), - ('warning-meson', 'warning', False), - ('failing-meson', 'failing', False), - ('failing-build', 'failing build', False), - ('failing-test', 'failing test', False), - ('kconfig', 'kconfig', False), - - ('platform-osx', 'osx', not mesonlib.is_osx()), - ('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()), - ('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()), - - ('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()), - ('C#', 'csharp', skip_csharp(backend)), - ('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))), - ('rust', 'rust', should_skip_rust(backend)), - ('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(options.use_tmpdir)), - ('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or not have_objcpp_compiler(options.use_tmpdir)), - ('fortran', 'fortran', skip_fortran or backend != Backend.ninja), - ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), + TestCategory('cmake', 'cmake', not shutil.which('cmake') or (os.environ.get('compiler') == 'msvc2015' and under_ci)), + TestCategory('common', 'common'), + TestCategory('warning-meson', 'warning', stdout_mandatory=True), + TestCategory('failing-meson', 'failing', stdout_mandatory=True), + TestCategory('failing-build', 'failing build'), + TestCategory('failing-test', 'failing test'), + TestCategory('keyval', 'keyval'), + TestCategory('platform-osx', 'osx', not mesonlib.is_osx()), + TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()), + TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()), + TestCategory('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()), + TestCategory('C#', 'csharp', skip_csharp(backend)), + TestCategory('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))), + TestCategory('rust', 'rust', should_skip_rust(backend)), + TestCategory('d', 'd', backend is not Backend.ninja or not have_d_compiler()), + TestCategory('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or not have_objc_compiler(options.use_tmpdir)), + TestCategory('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or not have_objcpp_compiler(options.use_tmpdir)), + TestCategory('fortran', 'fortran', skip_fortran or backend != Backend.ninja), + TestCategory('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), # CUDA tests on Windows: use Ninja backend: python run_project_tests.py --only cuda --backend ninja - ('cuda', 'cuda', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('nvcc')), - ('python3', 'python3', backend is not Backend.ninja), - ('python', 'python', backend is not Backend.ninja), - ('fpga', 'fpga', shutil.which('yosys') is None), - ('frameworks', 'frameworks', False), - ('nasm', 'nasm', False), - ('wasm', 'wasm', shutil.which('emcc') is None or backend is not Backend.ninja), + TestCategory('cuda', 'cuda', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('nvcc')), + TestCategory('python3', 'python3', backend is not Backend.ninja), + TestCategory('python', 'python', backend is not Backend.ninja), + TestCategory('fpga', 'fpga', shutil.which('yosys') is None), + TestCategory('frameworks', 'frameworks'), + TestCategory('nasm', 'nasm'), + TestCategory('wasm', 'wasm', shutil.which('emcc') is None or backend is not Backend.ninja), ] - names = [t[0] for t in all_tests] - assert names == ALL_TESTS, 'argparse("--only", choices=ALL_TESTS) need to be updated to match all_tests names' + categories = [t.category for t in all_tests] + assert categories == ALL_TESTS, 'argparse("--only", choices=ALL_TESTS) need to be updated to match all_tests categories' + if only: - ind = [names.index(o) for o in only] - all_tests = [all_tests[i] for i in ind] - gathered_tests = [(name, gather_tests(Path('test cases', subdir)), skip) for name, subdir, skip in all_tests] + all_tests = [t for t in all_tests if t.category in only] + + gathered_tests = [(t.category, gather_tests(Path('test cases', t.subdir), t.stdout_mandatory), t.skip) for t in all_tests] return gathered_tests def run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], @@ -1103,11 +1187,23 @@ def detect_system_compiler(options): def print_tool_versions(): tools = [ { + 'tool': 'ninja', + 'args': ['--version'], + 'regex': re.compile(r'^([0-9]+(\.[0-9]+)*(-[a-z0-9]+)?)$'), + 'match_group': 1, + }, + { 'tool': 'cmake', 'args': ['--version'], 'regex': re.compile(r'^cmake version ([0-9]+(\.[0-9]+)*(-[a-z0-9]+)?)$'), 'match_group': 1, }, + { + 'tool': 'hotdoc', + 'args': ['--version'], + 'regex': re.compile(r'^([0-9]+(\.[0-9]+)*(-[a-z0-9]+)?)$'), + 'match_group': 1, + }, ] def get_version(t: dict) -> str: @@ -1123,6 +1219,7 @@ def print_tool_versions(): i = i.strip('\n\r\t ') m = t['regex'].match(i) if m is not None: + tool_vers_map[t['tool']] = m.group(t['match_group']) return '{} ({})'.format(exe, m.group(t['match_group'])) return '{} (unknown)'.format(exe) diff --git a/run_tests.py b/run_tests.py index 005d9a0..44dcf82 100755 --- a/run_tests.py +++ b/run_tests.py @@ -303,7 +303,7 @@ def run_configure(commandlist, env=None): return run_configure_inprocess(commandlist, env=env) def print_system_info(): - print(mlog.bold('System information.').get_text(mlog.colorize_console)) + print(mlog.bold('System information.').get_text(mlog.colorize_console())) print('Architecture:', platform.architecture()) print('Machine:', platform.machine()) print('Platform:', platform.system()) @@ -377,7 +377,7 @@ def main(): print(flush=True) returncode = 0 else: - print(mlog.bold('Running unittests.').get_text(mlog.colorize_console)) + print(mlog.bold('Running unittests.').get_text(mlog.colorize_console())) print(flush=True) cmd = mesonlib.python_command + ['run_unittests.py', '-v'] if options.failfast: @@ -390,7 +390,7 @@ def main(): else: cross_test_args = mesonlib.python_command + ['run_cross_test.py'] for cf in options.cross: - print(mlog.bold('Running {} cross tests.'.format(cf)).get_text(mlog.colorize_console)) + print(mlog.bold('Running {} cross tests.'.format(cf)).get_text(mlog.colorize_console())) print(flush=True) cmd = cross_test_args + ['cross/' + cf] if options.failfast: diff --git a/run_unittests.py b/run_unittests.py index 831e53f..7e0c403 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -40,6 +40,7 @@ from contextlib import contextmanager from glob import glob from pathlib import (PurePath, Path) from distutils.dir_util import copy_tree +import typing import mesonbuild.mlog import mesonbuild.depfile @@ -56,7 +57,7 @@ from mesonbuild.mesonlib import ( BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos, windows_proof_rmtree, python_command, version_compare, split_args, - quote_arg, relpath + quote_arg, relpath, is_linux ) from mesonbuild.environment import detect_ninja from mesonbuild.mesonlib import MesonException, EnvironmentException @@ -717,25 +718,22 @@ class InternalTests(unittest.TestCase): self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) def test_pkgconfig_module(self): - - class Mock: - pass - - dummystate = Mock() + dummystate = mock.Mock() dummystate.subproject = 'dummy' - mock = Mock() - mock.pcdep = Mock() - mock.pcdep.name = "some_name" - mock.version_reqs = [] + _mock = mock.Mock(spec=mesonbuild.dependencies.ExternalDependency) + _mock.pcdep = mock.Mock() + _mock.pcdep.name = "some_name" + _mock.version_reqs = [] + _mock = mock.Mock(held_object=_mock) # pkgconfig dependency as lib deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib") - deps.add_pub_libs([mock]) + deps.add_pub_libs([_mock]) self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name") # pkgconfig dependency as requires deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib") - deps.add_pub_reqs([mock]) + deps.add_pub_reqs([_mock]) self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name") def _test_all_naming(self, cc, env, patterns, platform): @@ -1468,14 +1466,14 @@ class DataTests(unittest.TestCase): class BasePlatformTests(unittest.TestCase): + prefix = '/usr' + libdir = 'lib' def setUp(self): super().setUp() self.maxDiff = None src_root = os.path.dirname(__file__) src_root = os.path.join(os.getcwd(), src_root) self.src_root = src_root - self.prefix = '/usr' - self.libdir = 'lib' # Get the backend # FIXME: Extract this from argv? self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja')) @@ -1588,8 +1586,9 @@ class BasePlatformTests(unittest.TestCase): extra_args = [extra_args] args = [srcdir, self.builddir] if default_args: - args += ['--prefix', self.prefix, - '--libdir', self.libdir] + args += ['--prefix', self.prefix] + if self.libdir: + args += ['--libdir', self.libdir] if self.meson_native_file: args += ['--native-file', self.meson_native_file] if self.meson_cross_file: @@ -2448,9 +2447,12 @@ class AllPlatformTests(BasePlatformTests): # Check include order for 'someexe' incs = [a for a in split_args(execmd) if a.startswith("-I")] self.assertEqual(len(incs), 9) - # target private dir - someexe_id = Target.construct_id_from_path("sub4", "someexe", "@exe") - self.assertPathEqual(incs[0], "-I" + os.path.join("sub4", someexe_id)) + # Need to run the build so the private dir is created. + self.build() + pdirs = glob(os.path.join(self.builddir, 'sub4/someexe*.p')) + self.assertEqual(len(pdirs), 1) + privdir = pdirs[0][len(self.builddir)+1:] + self.assertPathEqual(incs[0], "-I" + privdir) # target build subdir self.assertPathEqual(incs[1], "-Isub4") # target source subdir @@ -2471,7 +2473,10 @@ class AllPlatformTests(BasePlatformTests): incs = [a for a in split_args(fxecmd) if a.startswith('-I')] self.assertEqual(len(incs), 9) # target private dir - self.assertPathEqual(incs[0], '-Isomefxe@exe') + pdirs = glob(os.path.join(self.builddir, 'somefxe*.p')) + self.assertEqual(len(pdirs), 1) + privdir = pdirs[0][len(self.builddir)+1:] + self.assertPathEqual(incs[0], '-I' + privdir) # target build dir self.assertPathEqual(incs[1], '-I.') # target source dir @@ -3419,67 +3424,6 @@ int main(int argc, char **argv) { f.write('public class Foo { public static void main() {} }') self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) - # The test uses mocking and thus requires that - # the current process is the one to run the Meson steps. - # If we are using an external test executable (most commonly - # in Debian autopkgtests) then the mocking won't work. - @unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.') - def test_cross_file_system_paths(self): - if is_windows(): - raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)') - if is_sunos(): - cc = 'gcc' - else: - cc = 'cc' - - testdir = os.path.join(self.common_test_dir, '1 trivial') - cross_content = textwrap.dedent("""\ - [binaries] - c = '/usr/bin/{}' - ar = '/usr/bin/ar' - strip = '/usr/bin/ar' - - [properties] - - [host_machine] - system = 'linux' - cpu_family = 'x86' - cpu = 'i686' - endian = 'little' - """.format(cc)) - - with tempfile.TemporaryDirectory() as d: - dir_ = os.path.join(d, 'meson', 'cross') - os.makedirs(dir_) - with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: - f.write(cross_content) - name = os.path.basename(f.name) - - with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}): - self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) - self.wipe() - - with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}): - os.environ.pop('XDG_DATA_HOME', None) - self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) - self.wipe() - - with tempfile.TemporaryDirectory() as d: - dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross') - os.makedirs(dir_) - with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: - f.write(cross_content) - name = os.path.basename(f.name) - - # If XDG_DATA_HOME is set in the environment running the - # tests this test will fail, os mock the environment, pop - # it, then test - with mock.patch.dict(os.environ): - os.environ.pop('XDG_DATA_HOME', None) - with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)): - self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) - self.wipe() - def test_compiler_run_command(self): ''' The test checks that the compiler object can be passed to @@ -4456,6 +4400,83 @@ recommended as it is not supported on some platforms''') self.maxDiff = None self.assertListEqual(res_nb, res_wb) + def test_introspect_ast_source(self): + testdir = os.path.join(self.unit_test_dir, '57 introspection') + testfile = os.path.join(testdir, 'meson.build') + res_nb = self.introspect_directory(testfile, ['--ast'] + self.meson_args) + + node_counter = {} + + def accept_node(json_node): + self.assertIsInstance(json_node, dict) + for i in ['lineno', 'colno', 'end_lineno', 'end_colno']: + self.assertIn(i, json_node) + self.assertIsInstance(json_node[i], int) + self.assertIn('node', json_node) + n = json_node['node'] + self.assertIsInstance(n, str) + self.assertIn(n, nodes) + if n not in node_counter: + node_counter[n] = 0 + node_counter[n] = node_counter[n] + 1 + for nodeDesc in nodes[n]: + key = nodeDesc[0] + func = nodeDesc[1] + self.assertIn(key, json_node) + if func is None: + tp = nodeDesc[2] + self.assertIsInstance(json_node[key], tp) + continue + func(json_node[key]) + + def accept_node_list(node_list): + self.assertIsInstance(node_list, list) + for i in node_list: + accept_node(i) + + def accept_kwargs(kwargs): + self.assertIsInstance(kwargs, list) + for i in kwargs: + self.assertIn('key', i) + self.assertIn('val', i) + accept_node(i['key']) + accept_node(i['val']) + + nodes = { + 'BooleanNode': [('value', None, bool)], + 'IdNode': [('value', None, str)], + 'NumberNode': [('value', None, int)], + 'StringNode': [('value', None, str)], + 'ContinueNode': [], + 'BreakNode': [], + 'ArgumentNode': [('positional', accept_node_list), ('kwargs', accept_kwargs)], + 'ArrayNode': [('args', accept_node)], + 'DictNode': [('args', accept_node)], + 'EmptyNode': [], + 'OrNode': [('left', accept_node), ('right', accept_node)], + 'AndNode': [('left', accept_node), ('right', accept_node)], + 'ComparisonNode': [('left', accept_node), ('right', accept_node), ('ctype', None, str)], + 'ArithmeticNode': [('left', accept_node), ('right', accept_node), ('op', None, str)], + 'NotNode': [('right', accept_node)], + 'CodeBlockNode': [('lines', accept_node_list)], + 'IndexNode': [('object', accept_node), ('index', accept_node)], + 'MethodNode': [('object', accept_node), ('args', accept_node), ('name', None, str)], + 'FunctionNode': [('args', accept_node), ('name', None, str)], + 'AssignmentNode': [('value', accept_node), ('var_name', None, str)], + 'PlusAssignmentNode': [('value', accept_node), ('var_name', None, str)], + 'ForeachClauseNode': [('items', accept_node), ('block', accept_node), ('varnames', None, list)], + 'IfClauseNode': [('ifs', accept_node_list), ('else', accept_node)], + 'IfNode': [('condition', accept_node), ('block', accept_node)], + 'UMinusNode': [('right', accept_node)], + 'TernaryNode': [('condition', accept_node), ('true', accept_node), ('false', accept_node)], + } + + accept_node(res_nb) + + for n, c in [('ContinueNode', 2), ('BreakNode', 1), ('NotNode', 3)]: + self.assertIn(n, node_counter) + self.assertEqual(node_counter[n], c) + def test_introspect_dependencies_from_source(self): testdir = os.path.join(self.unit_test_dir, '57 introspection') testfile = os.path.join(testdir, 'meson.build') @@ -4535,7 +4556,7 @@ recommended as it is not supported on some platforms''') self._run(self.mconf_command + [self.builddir]) def test_summary(self): - testdir = os.path.join(self.unit_test_dir, '72 summary') + testdir = os.path.join(self.unit_test_dir, '73 summary') out = self.init(testdir) expected = textwrap.dedent(r''' Some Subproject 2.0 @@ -4589,7 +4610,7 @@ recommended as it is not supported on some platforms''') self.assertPathDoesNotExist(os.path.join(self.builddir, prog)) def test_spurious_reconfigure_built_dep_file(self): - testdir = os.path.join(self.unit_test_dir, '74 dep files') + testdir = os.path.join(self.unit_test_dir, '75 dep files') # Regression test: Spurious reconfigure was happening when build # directory is inside source directory. @@ -4617,6 +4638,49 @@ recommended as it is not supported on some platforms''') out = self.build() self.assertNotIn('Project configured', out) + def _test_junit(self, case: str) -> None: + try: + import lxml.etree as et + except ImportError: + raise unittest.SkipTest('lxml required, but not found.') + + schema = et.XMLSchema(et.parse(str(Path(__file__).parent / 'data' / 'schema.xsd'))) + + self.init(case) + self.run_tests() + + junit = et.parse(str(Path(self.builddir) / 'meson-logs' / 'testlog.junit.xml')) + try: + schema.assertValid(junit) + except et.DocumentInvalid as e: + self.fail(e.error_log) + + def test_junit_valid_tap(self): + self._test_junit(os.path.join(self.common_test_dir, '213 tap tests')) + + def test_junit_valid_exitcode(self): + self._test_junit(os.path.join(self.common_test_dir, '44 test args')) + + def test_junit_valid_gtest(self): + self._test_junit(os.path.join(self.framework_test_dir, '2 gtest')) + + def test_link_language_linker(self): + # TODO: there should be some way to query how we're linking things + # without resorting to reading the ninja.build file + if self.backend is not Backend.ninja: + raise unittest.SkipTest('This test reads the ninja file') + + testdir = os.path.join(self.common_test_dir, '232 link language') + self.init(testdir) + + build_ninja = os.path.join(self.builddir, 'build.ninja') + with open(build_ninja, 'r', encoding='utf-8') as f: + contents = f.read() + + self.assertRegex(contents, r'build main(\.exe)?.*: c_LINKER') + self.assertRegex(contents, r'build (lib|cyg)?mylib.*: c_LINKER') + + class FailureTests(BasePlatformTests): ''' Tests that test failure conditions. Build files here should be dynamically @@ -5134,7 +5198,7 @@ class WindowsTests(BasePlatformTests): raise raise unittest.SkipTest('pefile module not found') testdir = os.path.join(self.common_test_dir, '6 linkshared') - self.init(testdir) + self.init(testdir, extra_args=['--buildtype=release']) self.build() # Test that binaries have a non-zero checksum env = get_fake_env() @@ -5282,7 +5346,7 @@ class DarwinTests(BasePlatformTests): def test_removing_unused_linker_args(self): testdir = os.path.join(self.common_test_dir, '108 has arg') - env = {'CFLAGS': '-L/tmp -L /var/tmp -headerpad_max_install_names -Wl,-export_dynamic'} + env = {'CFLAGS': '-L/tmp -L /var/tmp -headerpad_max_install_names -Wl,-export_dynamic -framework Foundation'} self.init(testdir, override_envvars=env) @@ -5533,6 +5597,10 @@ class LinuxlikeTests(BasePlatformTests): self.assertRegex('\n'.join(mesonlog), r'Run-time dependency qt5 \(modules: Core\) found: YES .* \((qmake|qmake-qt5)\)\n') + def glob_sofiles_without_privdir(self, g): + files = glob(g) + return [f for f in files if not f.endswith('.p')] + def _test_soname_impl(self, libpath, install): if is_cygwin() or is_osx(): raise unittest.SkipTest('Test only applicable to ELF and linuxlike sonames') @@ -5548,28 +5616,28 @@ class LinuxlikeTests(BasePlatformTests): self.assertPathExists(nover) self.assertFalse(os.path.islink(nover)) self.assertEqual(get_soname(nover), 'libnover.so') - self.assertEqual(len(glob(nover[:-3] + '*')), 1) + self.assertEqual(len(self.glob_sofiles_without_privdir(nover[:-3] + '*')), 1) # File with version set verset = os.path.join(libpath, 'libverset.so') self.assertPathExists(verset + '.4.5.6') self.assertEqual(os.readlink(verset), 'libverset.so.4') self.assertEqual(get_soname(verset), 'libverset.so.4') - self.assertEqual(len(glob(verset[:-3] + '*')), 3) + self.assertEqual(len(self.glob_sofiles_without_privdir(verset[:-3] + '*')), 3) # File with soversion set soverset = os.path.join(libpath, 'libsoverset.so') self.assertPathExists(soverset + '.1.2.3') self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3') self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3') - self.assertEqual(len(glob(soverset[:-3] + '*')), 2) + self.assertEqual(len(self.glob_sofiles_without_privdir(soverset[:-3] + '*')), 2) # File with version and soversion set to same values settosame = os.path.join(libpath, 'libsettosame.so') self.assertPathExists(settosame + '.7.8.9') self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9') self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9') - self.assertEqual(len(glob(settosame[:-3] + '*')), 2) + self.assertEqual(len(self.glob_sofiles_without_privdir(settosame[:-3] + '*')), 2) # File with version and soversion set to different values bothset = os.path.join(libpath, 'libbothset.so') @@ -5577,7 +5645,7 @@ class LinuxlikeTests(BasePlatformTests): self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3') self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6') self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3') - self.assertEqual(len(glob(bothset[:-3] + '*')), 3) + self.assertEqual(len(self.glob_sofiles_without_privdir(bothset[:-3] + '*')), 3) def test_soname(self): self._test_soname_impl(self.builddir, False) @@ -5697,10 +5765,12 @@ class LinuxlikeTests(BasePlatformTests): def test_unity_subproj(self): testdir = os.path.join(self.common_test_dir, '45 subproject') self.init(testdir, extra_args='--unity=subprojects') - simpletest_id = Target.construct_id_from_path('subprojects/sublib', 'simpletest', '@exe') - self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', simpletest_id, 'simpletest-unity0.c')) - sublib_id = Target.construct_id_from_path('subprojects/sublib', 'sublib', '@sha') - self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', sublib_id, 'sublib-unity0.c')) + pdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/simpletest*.p')) + self.assertEqual(len(pdirs), 1) + self.assertPathExists(os.path.join(pdirs[0], 'simpletest-unity0.c')) + sdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/*sublib*.p')) + self.assertEqual(len(sdirs), 1) + self.assertPathExists(os.path.join(sdirs[0], 'sublib-unity0.c')) self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c')) self.build() @@ -6036,6 +6106,39 @@ class LinuxlikeTests(BasePlatformTests): install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx')) self.assertEqual(install_rpath, 'baz') + def test_global_rpath(self): + if is_cygwin(): + raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') + if is_osx(): + raise unittest.SkipTest('Global RPATHs via LDFLAGS not yet supported on MacOS (does anybody need it?)') + + testdir = os.path.join(self.unit_test_dir, '77 global-rpath') + oldinstalldir = self.installdir + + # Build and install an external library without DESTDIR. + # The external library generates a .pc file without an rpath. + yonder_dir = os.path.join(testdir, 'yonder') + yonder_prefix = os.path.join(oldinstalldir, 'yonder') + yonder_libdir = os.path.join(yonder_prefix, self.libdir) + self.prefix = yonder_prefix + self.installdir = yonder_prefix + self.init(yonder_dir) + self.build() + self.install(use_destdir=False) + self.new_builddir() + + # Build an app that uses that installed library. + # Supply the rpath to the installed library via LDFLAGS + # (as systems like buildroot and guix are wont to do) + # and verify install preserves that rpath. + env = {'LDFLAGS': '-Wl,-rpath=' + yonder_libdir, + 'PKG_CONFIG_PATH': os.path.join(yonder_libdir, 'pkgconfig')} + self.init(testdir, override_envvars=env) + self.build() + self.install(use_destdir=False) + got_rpath = get_rpath(os.path.join(yonder_prefix, 'bin/rpathified')) + self.assertEqual(got_rpath, yonder_libdir) + @skip_if_not_base_option('b_sanitize') def test_pch_with_address_sanitizer(self): if is_cygwin(): @@ -6347,13 +6450,15 @@ class LinuxlikeTests(BasePlatformTests): self.build(override_envvars=env) # test uninstalled self.run_tests(override_envvars=env) - if not is_osx(): - # Rest of the workflow only works on macOS + if not (is_osx() or is_linux()): return # test running after installation self.install(use_destdir=False) prog = os.path.join(self.installdir, 'bin', 'prog') self._run([prog]) + if not is_osx(): + # Rest of the workflow only works on macOS + return out = self._run(['otool', '-L', prog]) self.assertNotIn('@rpath', out) ## New builddir for testing that DESTDIR is not added to install_name @@ -6370,6 +6475,57 @@ class LinuxlikeTests(BasePlatformTests): # Ensure that the otool output does not contain self.installdir self.assertNotRegex(out, self.installdir + '.*dylib ') + @skipIfNoPkgconfig + def test_usage_pkgconfig_prefixes(self): + ''' + Build and install two external libraries, to different prefixes, + then build and install a client program that finds them via pkgconfig, + and verify the installed client program runs. + ''' + oldinstalldir = self.installdir + + # Build and install both external libraries without DESTDIR + val1dir = os.path.join(self.unit_test_dir, '76 pkgconfig prefixes', 'val1') + val1prefix = os.path.join(oldinstalldir, 'val1') + self.prefix = val1prefix + self.installdir = val1prefix + self.init(val1dir) + self.build() + self.install(use_destdir=False) + self.new_builddir() + + env1 = {} + env1['PKG_CONFIG_PATH'] = os.path.join(val1prefix, self.libdir, 'pkgconfig') + val2dir = os.path.join(self.unit_test_dir, '76 pkgconfig prefixes', 'val2') + val2prefix = os.path.join(oldinstalldir, 'val2') + self.prefix = val2prefix + self.installdir = val2prefix + self.init(val2dir, override_envvars=env1) + self.build() + self.install(use_destdir=False) + self.new_builddir() + + # Build, install, and run the client program + env2 = {} + env2['PKG_CONFIG_PATH'] = os.path.join(val2prefix, self.libdir, 'pkgconfig') + testdir = os.path.join(self.unit_test_dir, '76 pkgconfig prefixes', 'client') + testprefix = os.path.join(oldinstalldir, 'client') + self.prefix = testprefix + self.installdir = testprefix + self.init(testdir, override_envvars=env2) + self.build() + self.install(use_destdir=False) + prog = os.path.join(self.installdir, 'bin', 'client') + env3 = {} + if is_cygwin(): + env3['PATH'] = os.path.join(val1prefix, 'bin') + \ + os.pathsep + \ + os.path.join(val2prefix, 'bin') + \ + os.pathsep + os.environ['PATH'] + out = self._run([prog], override_envvars=env3).strip() + # Expected output is val1 + val2 = 3 + self.assertEqual(out, '3') + def install_subdir_invalid_symlinks(self, testdir, subdir_path): ''' Test that installation of broken symlinks works fine. @@ -6560,7 +6716,7 @@ c = ['{0}'] return hashlib.sha256(f.read()).hexdigest() def test_wrap_with_file_url(self): - testdir = os.path.join(self.unit_test_dir, '73 wrap file url') + testdir = os.path.join(self.unit_test_dir, '74 wrap file url') source_filename = os.path.join(testdir, 'subprojects', 'foo.tar.xz') patch_filename = os.path.join(testdir, 'subprojects', 'foo-patch.tar.xz') wrap_filename = os.path.join(testdir, 'subprojects', 'foo.wrap') @@ -6591,11 +6747,17 @@ c = ['{0}'] os.unlink(wrap_filename) +class BaseLinuxCrossTests(BasePlatformTests): + # Don't pass --libdir when cross-compiling. We have tests that + # check whether meson auto-detects it correctly. + libdir = None + + def should_run_cross_arm_tests(): return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') @unittest.skipUnless(not is_windows() and should_run_cross_arm_tests(), "requires ability to cross compile to ARM") -class LinuxCrossArmTests(BasePlatformTests): +class LinuxCrossArmTests(BaseLinuxCrossTests): ''' Tests that cross-compilation to Linux/ARM works ''' @@ -6642,6 +6804,17 @@ class LinuxCrossArmTests(BasePlatformTests): return self.assertTrue(False, 'Option libdir not in introspect data.') + def test_cross_libdir_subproject(self): + # Guard against a regression where calling "subproject" + # would reset the value of libdir to its default value. + testdir = os.path.join(self.unit_test_dir, '76 subdir libdir') + self.init(testdir, extra_args=['--libdir=fuf']) + for i in self.introspect('--buildoptions'): + if i['name'] == 'libdir': + self.assertEqual(i['value'], 'fuf') + return + self.assertTrue(False, 'Libdir specified on command line gets reset.') + def test_std_remains(self): # C_std defined in project options must be in effect also when cross compiling. testdir = os.path.join(self.unit_test_dir, '51 noncross options') @@ -6665,7 +6838,7 @@ def should_run_cross_mingw_tests(): return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin()) @unittest.skipUnless(not is_windows() and should_run_cross_mingw_tests(), "requires ability to cross compile with MinGW") -class LinuxCrossMingwTests(BasePlatformTests): +class LinuxCrossMingwTests(BaseLinuxCrossTests): ''' Tests that cross-compilation to Windows/MinGW works ''' @@ -7459,6 +7632,134 @@ class CrossFileTests(BasePlatformTests): This is mainly aimed to testing overrides from cross files. """ + def _cross_file_generator(self, *, needs_exe_wrapper: bool = False, + exe_wrapper: typing.Optional[typing.List[str]] = None) -> str: + if is_windows(): + raise unittest.SkipTest('Cannot run this test on non-mingw/non-cygwin windows') + if is_sunos(): + cc = 'gcc' + else: + cc = 'cc' + + return textwrap.dedent("""\ + [binaries] + c = '/usr/bin/{}' + ar = '/usr/bin/ar' + strip = '/usr/bin/ar' + {} + + [properties] + needs_exe_wrapper = {} + + [host_machine] + system = 'linux' + cpu_family = 'x86' + cpu = 'i686' + endian = 'little' + """.format(cc, + 'exe_wrapper = {}'.format(str(exe_wrapper)) if exe_wrapper is not None else '', + needs_exe_wrapper)) + + def _stub_exe_wrapper(self) -> str: + return textwrap.dedent('''\ + #!/usr/bin/env python3 + import subprocess + import sys + + sys.exit(subprocess.run(sys.argv[1:]).returncode) + ''') + + def test_needs_exe_wrapper_true(self): + testdir = os.path.join(self.unit_test_dir, '72 cross test passed') + with tempfile.TemporaryDirectory() as d: + p = Path(d) / 'crossfile' + with p.open('wt') as f: + f.write(self._cross_file_generator(needs_exe_wrapper=True)) + self.init(testdir, extra_args=['--cross-file=' + str(p)]) + out = self.run_target('test') + self.assertRegex(out, r'Skipped:\s*1\s*\n') + + def test_needs_exe_wrapper_false(self): + testdir = os.path.join(self.unit_test_dir, '72 cross test passed') + with tempfile.TemporaryDirectory() as d: + p = Path(d) / 'crossfile' + with p.open('wt') as f: + f.write(self._cross_file_generator(needs_exe_wrapper=False)) + self.init(testdir, extra_args=['--cross-file=' + str(p)]) + out = self.run_target('test') + self.assertNotRegex(out, r'Skipped:\s*1\n') + + def test_needs_exe_wrapper_true_wrapper(self): + testdir = os.path.join(self.unit_test_dir, '72 cross test passed') + with tempfile.TemporaryDirectory() as d: + s = Path(d) / 'wrapper.py' + with s.open('wt') as f: + f.write(self._stub_exe_wrapper()) + s.chmod(0o774) + p = Path(d) / 'crossfile' + with p.open('wt') as f: + f.write(self._cross_file_generator( + needs_exe_wrapper=True, + exe_wrapper=[str(s)])) + + self.init(testdir, extra_args=['--cross-file=' + str(p), '-Dexpect=true']) + out = self.run_target('test') + self.assertRegex(out, r'Ok:\s*3\s*\n') + + def test_cross_exe_passed_no_wrapper(self): + testdir = os.path.join(self.unit_test_dir, '72 cross test passed') + with tempfile.TemporaryDirectory() as d: + p = Path(d) / 'crossfile' + with p.open('wt') as f: + f.write(self._cross_file_generator(needs_exe_wrapper=True)) + + self.init(testdir, extra_args=['--cross-file=' + str(p)]) + self.build() + out = self.run_target('test') + self.assertRegex(out, r'Skipped:\s*1\s*\n') + + # The test uses mocking and thus requires that the current process is the + # one to run the Meson steps. If we are using an external test executable + # (most commonly in Debian autopkgtests) then the mocking won't work. + @unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.') + def test_cross_file_system_paths(self): + if is_windows(): + raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)') + + testdir = os.path.join(self.common_test_dir, '1 trivial') + cross_content = self._cross_file_generator() + with tempfile.TemporaryDirectory() as d: + dir_ = os.path.join(d, 'meson', 'cross') + os.makedirs(dir_) + with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: + f.write(cross_content) + name = os.path.basename(f.name) + + with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}): + self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) + self.wipe() + + with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}): + os.environ.pop('XDG_DATA_HOME', None) + self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) + self.wipe() + + with tempfile.TemporaryDirectory() as d: + dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross') + os.makedirs(dir_) + with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: + f.write(cross_content) + name = os.path.basename(f.name) + + # If XDG_DATA_HOME is set in the environment running the + # tests this test will fail, os mock the environment, pop + # it, then test + with mock.patch.dict(os.environ): + os.environ.pop('XDG_DATA_HOME', None) + with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)): + self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) + self.wipe() + def test_cross_file_dirs(self): testcase = os.path.join(self.unit_test_dir, '60 native file override') self.init(testcase, default_args=False, diff --git a/test cases/cmake/2 advanced/test.json b/test cases/cmake/2 advanced/test.json index 11aad94..e12f530 100644 --- a/test cases/cmake/2 advanced/test.json +++ b/test cases/cmake/2 advanced/test.json @@ -4,5 +4,8 @@ {"type": "implib", "platform": "cygwin", "file": "usr/lib/libcm_cmModLib"}, {"type": "implib", "platform": "!cygwin", "file": "usr/bin/libcm_cmModLib"}, {"type": "exe", "file": "usr/bin/cm_testEXE"} - ] + ], + "tools": { + "cmake": ">=3.11" + } } diff --git a/test cases/cmake/3 advanced no dep/test.json b/test cases/cmake/3 advanced no dep/test.json index 24c89c4..98a1719 100644 --- a/test cases/cmake/3 advanced no dep/test.json +++ b/test cases/cmake/3 advanced no dep/test.json @@ -8,5 +8,8 @@ {"type": "exe", "file": "usr/bin/cm_testEXE"}, {"type": "pdb", "file": "usr/bin/cm_testEXE2"}, {"type": "exe", "file": "usr/bin/cm_testEXE2"} - ] + ], + "tools": { + "cmake": ">=3.11" + } } diff --git a/test cases/common/104 postconf with args/meson.build b/test cases/common/104 postconf with args/meson.build index 8510c5b..a34502c 100644 --- a/test cases/common/104 postconf with args/meson.build +++ b/test cases/common/104 postconf with args/meson.build @@ -1,5 +1,10 @@ project('postconf script', 'c') -meson.add_postconf_script('postconf.py', '5', '33') +conf = configure_file( + configuration : configuration_data(), + output : 'out' +) + +meson.add_postconf_script(find_program('postconf.py'), '5', '33', conf) test('post', executable('prog', 'prog.c')) diff --git a/test cases/common/163 disabler/meson.build b/test cases/common/163 disabler/meson.build index 5554f14..d132e2b 100644 --- a/test cases/common/163 disabler/meson.build +++ b/test cases/common/163 disabler/meson.build @@ -9,6 +9,7 @@ d2 = dependency(d) d3 = (d == d2) d4 = d + 0 d5 = d2 or true +set_variable('d6', disabler()) has_not_changed = false if is_disabler(d) @@ -23,12 +24,14 @@ assert(is_disabler(d2), 'Function laundered disabler was not identified correctl assert(is_disabler(d3), 'Disabler comparison should yield disabler.') assert(is_disabler(d4), 'Disabler addition should yield disabler.') assert(is_disabler(d5), 'Disabler logic op should yield disabler.') +assert(is_disabler(d6), 'set_variable with a disabler should set variable to disabler.') assert(d, 'Disabler did not cause this to be skipped.') assert(d2, 'Function laundered disabler did not cause this to be skipped.') assert(d3, 'Disabler comparison should yield disabler and thus this would not be called.') assert(d4, 'Disabler addition should yield disabler and thus this would not be called.') assert(d5, 'Disabler logic op should yield disabler and thus this would not be called.') +assert(d6, 'set_variable with a disabler did not cause this to be skipped.') number = 0 @@ -80,6 +83,31 @@ else endif assert(has_not_changed, 'App has changed.') +assert(not is_disabler(is_variable('d6')), 'is_variable should not return a disabler') +assert(is_variable('d6'), 'is_variable for a disabler should return true') + +if_is_not_disabled = false +if is_variable('d6') + if_is_not_disabled = true +else + if_is_not_disabled = true +endif +assert(if_is_not_disabled, 'Disabler in is_variable should not skip blocks') + +get_d = get_variable('d6') +assert(is_disabler(get_d), 'get_variable should yield a disabler') + +get_fallback_d = get_variable('nonexistant', disabler()) +assert(is_disabler(get_fallback_d), 'get_variable fallback should yield a disabler') + +var_true = true +get_no_fallback_d = get_variable('var_true', disabler()) +assert(not is_disabler(get_no_fallback_d), 'get_variable should not fallback to disabler') +assert(get_no_fallback_d, 'get_variable should yield true') + +assert(is_disabler(get_variable(disabler())), 'get_variable should yield a disabler') +assert(is_disabler(get_variable(disabler(), var_true)), 'get_variable should yield a disabler') + if_is_disabled = true if disabler() if_is_disabled = false diff --git a/test cases/common/201 override with exe/meson.build b/test cases/common/201 override with exe/meson.build index 81f6c02..62d2f32 100644 --- a/test cases/common/201 override with exe/meson.build +++ b/test cases/common/201 override with exe/meson.build @@ -1,6 +1,10 @@ project('myexe', 'c') sub = subproject('sub') -prog = find_program('foobar') + +prog = find_program('foobar', version : '>= 2.0', required : false) +assert(not prog.found()) + +prog = find_program('foobar', version : '>= 1.0') custom1 = custom_target('custom1', build_by_default : true, input : [], @@ -11,5 +15,7 @@ gen = generator(prog, arguments : ['@OUTPUT@']) custom2 = gen.process('main2.input') +message(prog.full_path()) + executable('e1', custom1) executable('e2', custom2) diff --git a/test cases/common/201 override with exe/subprojects/sub/meson.build b/test cases/common/201 override with exe/subprojects/sub/meson.build index 1f186da..f0343b2 100644 --- a/test cases/common/201 override with exe/subprojects/sub/meson.build +++ b/test cases/common/201 override with exe/subprojects/sub/meson.build @@ -1,3 +1,3 @@ -project('sub', 'c') +project('sub', 'c', version : '1.0') foobar = executable('foobar', 'foobar.c', native : true) meson.override_find_program('foobar', foobar) diff --git a/test cases/common/222 source set realistic example/meson.build b/test cases/common/222 source set realistic example/meson.build index 5b0e495..106b81d 100644 --- a/test cases/common/222 source set realistic example/meson.build +++ b/test cases/common/222 source set realistic example/meson.build @@ -1,4 +1,4 @@ -# a sort-of realistic example that combines the sourceset and kconfig +# a sort-of realistic example that combines the sourceset and keyval # modules, inspired by QEMU's build system project('sourceset-example', 'cpp', default_options: ['cpp_std=c++11']) @@ -9,7 +9,7 @@ if cppid == 'pgi' endif ss = import('sourceset') -kconfig = import('unstable-kconfig') +keyval = import('unstable-keyval') zlib = declare_dependency(compile_args: '-DZLIB=1') another = declare_dependency(compile_args: '-DANOTHER=1') @@ -39,7 +39,7 @@ targets = [ 'arm', 'aarch64', 'x86' ] target_dirs = { 'arm' : 'arm', 'aarch64' : 'arm', 'x86': 'x86' } foreach x : targets - config = kconfig.load('config' / x) + config = keyval.load('config' / x) target_specific = specific.apply(config, strict: false) target_common = common.apply(config, strict: false) target_deps = target_specific.dependencies() + target_common.dependencies() diff --git a/test cases/common/226 include_type dependency/main.cpp b/test cases/common/226 include_type dependency/main.cpp new file mode 100644 index 0000000..bf8c4a4 --- /dev/null +++ b/test cases/common/226 include_type dependency/main.cpp @@ -0,0 +1,8 @@ +#include <iostream> +#include <boost/graph/filtered_graph.hpp> + +using namespace std; + +int main(void) { + return 0; +} diff --git a/test cases/common/226 include_type dependency/meson.build b/test cases/common/226 include_type dependency/meson.build index fafceaf..d17e920 100644 --- a/test cases/common/226 include_type dependency/meson.build +++ b/test cases/common/226 include_type dependency/meson.build @@ -4,10 +4,16 @@ project( ) dep = dependency('zlib', method: 'pkg-config', required : false) +boost_dep = dependency('boost', modules: ['graph'], include_type : 'system', required: false) + if not dep.found() error('MESON_SKIP_TEST zlib was not found') endif +if not boost_dep.found() + error('MESON_SKIP_TEST boost was not found') +endif + assert(dep.include_type() == 'preserve', 'include_type must default to "preserve"') dep_sys = dep.as_system() @@ -26,3 +32,7 @@ assert(sp_dep.include_type() == 'preserve', 'default is preserve') sp_dep_sys = sp_dep.as_system('system') assert(sp_dep_sys.include_type() == 'system', 'changing include_type works') assert(sp_dep.include_type() == 'preserve', 'as_system must not mutate the original object') + +# Check that PCH works with `include_type : 'system'` See https://github.com/mesonbuild/meson/issues/7167 +main_exe = executable('main_exe', 'main.cpp', cpp_pch: 'pch/test.hpp', dependencies: boost_dep) +test('main_test', main_exe) diff --git a/test cases/common/226 include_type dependency/pch/test.hpp b/test cases/common/226 include_type dependency/pch/test.hpp new file mode 100644 index 0000000..0d40fe1 --- /dev/null +++ b/test cases/common/226 include_type dependency/pch/test.hpp @@ -0,0 +1 @@ +#include <boost/graph/filtered_graph.hpp> diff --git a/test cases/common/232 link language/c_linkage.cpp b/test cases/common/232 link language/c_linkage.cpp new file mode 100644 index 0000000..dc006b9 --- /dev/null +++ b/test cases/common/232 link language/c_linkage.cpp @@ -0,0 +1,5 @@ +extern "C" { + int makeInt(void) { + return 0; + } +} diff --git a/test cases/common/232 link language/c_linkage.h b/test cases/common/232 link language/c_linkage.h new file mode 100644 index 0000000..1609f47 --- /dev/null +++ b/test cases/common/232 link language/c_linkage.h @@ -0,0 +1,10 @@ + +#ifdef __cplusplus +extern "C" { +#endif + +int makeInt(void); + +#ifdef __cplusplus +} +#endif diff --git a/test cases/common/232 link language/lib.cpp b/test cases/common/232 link language/lib.cpp new file mode 100644 index 0000000..ab43828 --- /dev/null +++ b/test cases/common/232 link language/lib.cpp @@ -0,0 +1,5 @@ +extern "C" { + int makeInt(void) { + return 1; + } +} diff --git a/test cases/common/232 link language/main.c b/test cases/common/232 link language/main.c new file mode 100644 index 0000000..5a167e7 --- /dev/null +++ b/test cases/common/232 link language/main.c @@ -0,0 +1,5 @@ +#include "c_linkage.h" + +int main(void) { + return makeInt(); +} diff --git a/test cases/common/232 link language/meson.build b/test cases/common/232 link language/meson.build new file mode 100644 index 0000000..f9af6cd --- /dev/null +++ b/test cases/common/232 link language/meson.build @@ -0,0 +1,18 @@ +project( + 'link_language', + ['c', 'cpp'], +) + +exe = executable( + 'main', + ['main.c', 'c_linkage.cpp'], + link_language : 'c', +) + +lib = library( + 'mylib', + ['lib.cpp'], + link_language : 'c', +) + +test('main', exe) diff --git a/test cases/common/233 link depends indexed custom target/foo.c b/test cases/common/233 link depends indexed custom target/foo.c new file mode 100644 index 0000000..58c86a6 --- /dev/null +++ b/test cases/common/233 link depends indexed custom target/foo.c @@ -0,0 +1,15 @@ +#include <stdio.h> + +int main(void) { + const char *fn = DEPFILE; + FILE *f = fopen(fn, "r"); + if (!f) { + printf("could not open %s", fn); + return 1; + } + else { + printf("successfully opened %s", fn); + } + + return 0; +} diff --git a/test cases/common/233 link depends indexed custom target/make_file.py b/test cases/common/233 link depends indexed custom target/make_file.py new file mode 100644 index 0000000..6a43b7d --- /dev/null +++ b/test cases/common/233 link depends indexed custom target/make_file.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +import sys + +with open(sys.argv[1], 'w') as f: + print('# this file does nothing', file=f) + +with open(sys.argv[2], 'w') as f: + print('# this file does nothing', file=f) diff --git a/test cases/common/233 link depends indexed custom target/meson.build b/test cases/common/233 link depends indexed custom target/meson.build new file mode 100644 index 0000000..5c066e9 --- /dev/null +++ b/test cases/common/233 link depends indexed custom target/meson.build @@ -0,0 +1,19 @@ +project('link_depends_indexed_custom_target', 'c') + +if meson.backend().startswith('vs') + # FIXME: Broken on the VS backends + error('MESON_SKIP_TEST see https://github.com/mesonbuild/meson/issues/1799') +endif + +cmd = find_program('make_file.py') + +dep_files = custom_target('gen_dep', + command: [cmd, '@OUTPUT@'], + output: ['dep_file1', 'dep_file2']) + +exe = executable('foo', 'foo.c', + link_depends: dep_files[1], + c_args: ['-DDEPFILE="' + dep_files[0].full_path()+ '"']) + +# check that dep_file1 exists, which means that link_depends target ran +test('runtest', exe) diff --git a/test cases/common/36 tryrun/meson.build b/test cases/common/36 tryrun/meson.build index 261adf2..5580974 100644 --- a/test cases/common/36 tryrun/meson.build +++ b/test cases/common/36 tryrun/meson.build @@ -2,7 +2,7 @@ project('tryrun', 'c', 'cpp') # Complex to exercise all code paths. if meson.is_cross_build() - if meson.has_exe_wrapper() + if meson.can_run_host_binaries() compilers = [meson.get_compiler('c', native : false), meson.get_compiler('cpp', native : false)] else compilers = [meson.get_compiler('c', native : true), meson.get_compiler('cpp', native : true)] diff --git a/test cases/common/43 options/meson_options.txt b/test cases/common/43 options/meson_options.txt index c5986ba..db649de 100644 --- a/test cases/common/43 options/meson_options.txt +++ b/test cases/common/43 options/meson_options.txt @@ -1,7 +1,7 @@ -option('testoption', type : 'string', value : 'optval', description : 'An option to do something') +option('testoption', type : 'string', value : 'optval', description : 'An option ' + 'to do something') option('other_one', type : 'boolean', value : not (not (not (not false)))) -option('combo_opt', type : 'combo', choices : ['one', 'two', 'combo'], value : 'combo') +option('combo_opt', type : 'co' + 'mbo', choices : ['one', 'two', 'combo'], value : 'combo') option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two']) option('free_array_opt', type : 'array') option('integer_opt', type : 'integer', min : 0, max : -(-5), value : 3) -option('neg_int_opt', type : 'integer', min : -5, max : 5, value : -3) +option('neg' + '_' + 'int' + '_' + 'opt', type : 'integer', min : -5, max : 5, value : -3) diff --git a/test cases/common/56 install script/customtarget.py b/test cases/common/56 install script/customtarget.py new file mode 100755 index 0000000..e28373a --- /dev/null +++ b/test cases/common/56 install script/customtarget.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import argparse +import os + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument('dirname') + args = parser.parse_args() + + with open(os.path.join(args.dirname, '1.txt'), 'w') as f: + f.write('') + with open(os.path.join(args.dirname, '2.txt'), 'w') as f: + f.write('') + + +if __name__ == "__main__": + main() diff --git a/test cases/common/56 install script/meson.build b/test cases/common/56 install script/meson.build index 6351518..e80e666 100644 --- a/test cases/common/56 install script/meson.build +++ b/test cases/common/56 install script/meson.build @@ -5,3 +5,29 @@ meson.add_install_script('myinstall.py', 'diiba/daaba', 'file.dat') meson.add_install_script('myinstall.py', 'this/should', 'also-work.dat') subdir('src') + +meson.add_install_script('myinstall.py', 'dir', afile, '--mode=copy') + +data = configuration_data() +data.set10('foo', true) +conf = configure_file( + configuration : data, + output : 'conf.txt' +) + +meson.add_install_script('myinstall.py', 'dir', conf, '--mode=copy') + +t = custom_target( + 'ct', + command : [find_program('customtarget.py'), '@OUTDIR@'], + output : ['1.txt', '2.txt'], +) + +meson.add_install_script('myinstall.py', 'customtarget', t, '--mode=copy') +meson.add_install_script('myinstall.py', 'customtargetindex', t[0], '--mode=copy') + +meson.add_install_script(exe, 'generated.txt') +wrap = find_program('wrap.py') +# Yes, these are getting silly +meson.add_install_script(wrap, exe, 'wrapped.txt') +meson.add_install_script(wrap, wrap, exe, 'wrapped2.txt') diff --git a/test cases/common/56 install script/myinstall.py b/test cases/common/56 install script/myinstall.py index 812561e..a573342 100644 --- a/test cases/common/56 install script/myinstall.py +++ b/test cases/common/56 install script/myinstall.py @@ -1,12 +1,31 @@ #!/usr/bin/env python3 +import argparse import os -import sys +import shutil prefix = os.environ['MESON_INSTALL_DESTDIR_PREFIX'] -dirname = os.path.join(prefix, sys.argv[1]) -os.makedirs(dirname) -with open(os.path.join(dirname, sys.argv[2]), 'w') as f: - f.write('') +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument('dirname') + parser.add_argument('files', nargs='+') + parser.add_argument('--mode', action='store', default='create', choices=['create', 'copy']) + args = parser.parse_args() + + dirname = os.path.join(prefix, args.dirname) + if not os.path.exists(dirname): + os.makedirs(dirname) + + if args.mode == 'create': + for name in args.files: + with open(os.path.join(dirname, name), 'w') as f: + f.write('') + else: + for name in args.files: + shutil.copy(name, dirname) + + +if __name__ == "__main__": + main() diff --git a/test cases/unit/74 dep files/foo.c b/test cases/common/56 install script/src/a file.txt index e69de29..e69de29 100644 --- a/test cases/unit/74 dep files/foo.c +++ b/test cases/common/56 install script/src/a file.txt diff --git a/test cases/common/56 install script/src/exe.c b/test cases/common/56 install script/src/exe.c new file mode 100644 index 0000000..b573b91 --- /dev/null +++ b/test cases/common/56 install script/src/exe.c @@ -0,0 +1,24 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int main(int argc, char * argv[]) { + if (argc != 2) { + fprintf(stderr, "Takes exactly 2 arguments\n"); + return 1; + } + + char * dirname = getenv("MESON_INSTALL_DESTDIR_PREFIX"); + char * fullname = malloc(strlen(dirname) + 1 + strlen(argv[1]) + 1); + strcpy(fullname, dirname); + strcat(fullname, "/"); + strcat(fullname, argv[1]); + + FILE * fp = fopen(fullname, "w"); + fputs("Some text\n", fp); + fclose(fp); + + free(fullname); + + return 0; +} diff --git a/test cases/common/56 install script/src/meson.build b/test cases/common/56 install script/src/meson.build index b23574a..1db424f 100644 --- a/test cases/common/56 install script/src/meson.build +++ b/test cases/common/56 install script/src/meson.build @@ -1 +1,5 @@ meson.add_install_script('myinstall.py', 'this/does', 'something-different.dat') + +afile = files('a file.txt') + +exe = executable('exe', 'exe.c', install : false, native : true) diff --git a/test cases/common/56 install script/src/myinstall.py b/test cases/common/56 install script/src/myinstall.py index 3b7ce37..3a9d89b 100644 --- a/test cases/common/56 install script/src/myinstall.py +++ b/test cases/common/56 install script/src/myinstall.py @@ -7,6 +7,8 @@ prefix = os.environ['MESON_INSTALL_DESTDIR_PREFIX'] dirname = os.path.join(prefix, sys.argv[1]) -os.makedirs(dirname) +if not os.path.exists(dirname): + os.makedirs(dirname) + with open(os.path.join(dirname, sys.argv[2] + '.in'), 'w') as f: f.write('') diff --git a/test cases/common/56 install script/test.json b/test cases/common/56 install script/test.json index d17625f..b2a5971 100644 --- a/test cases/common/56 install script/test.json +++ b/test cases/common/56 install script/test.json @@ -4,6 +4,14 @@ {"type": "pdb", "file": "usr/bin/prog"}, {"type": "file", "file": "usr/diiba/daaba/file.dat"}, {"type": "file", "file": "usr/this/should/also-work.dat"}, - {"type": "file", "file": "usr/this/does/something-different.dat.in"} + {"type": "file", "file": "usr/this/does/something-different.dat.in"}, + {"type": "file", "file": "usr/dir/a file.txt"}, + {"type": "file", "file": "usr/dir/conf.txt"}, + {"type": "file", "file": "usr/customtarget/1.txt"}, + {"type": "file", "file": "usr/customtarget/2.txt"}, + {"type": "file", "file": "usr/customtargetindex/1.txt"}, + {"type": "file", "file": "usr/generated.txt"}, + {"type": "file", "file": "usr/wrapped.txt"}, + {"type": "file", "file": "usr/wrapped2.txt"} ] } diff --git a/test cases/common/56 install script/wrap.py b/test cases/common/56 install script/wrap.py new file mode 100755 index 0000000..87508e0 --- /dev/null +++ b/test cases/common/56 install script/wrap.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import subprocess +import sys + +subprocess.run(sys.argv[1:]) diff --git a/test cases/common/93 selfbuilt custom/meson.build b/test cases/common/93 selfbuilt custom/meson.build index 3cc3906..b536352 100644 --- a/test cases/common/93 selfbuilt custom/meson.build +++ b/test cases/common/93 selfbuilt custom/meson.build @@ -26,7 +26,7 @@ ctlib = custom_target('ctlib', build_by_default : true, ) -if meson.is_cross_build() and meson.has_exe_wrapper() +if meson.is_cross_build() and meson.can_run_host_binaries() checkarg_host = executable('checkarg_host', 'checkarg.cpp') ctlib_host = custom_target( diff --git a/test cases/failing/1 project not first/test.json b/test cases/failing/1 project not first/test.json new file mode 100644 index 0000000..70f3c41 --- /dev/null +++ b/test cases/failing/1 project not first/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "ERROR: First statement must be a call to project" + } + ] +} diff --git a/test cases/failing/10 out of bounds/test.json b/test cases/failing/10 out of bounds/test.json new file mode 100644 index 0000000..e27d990 --- /dev/null +++ b/test cases/failing/10 out of bounds/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/10 out of bounds/meson.build:4:0: ERROR: Index 0 out of bounds of array of size 0." + } + ] +} diff --git a/test cases/failing/100 fallback consistency/test.json b/test cases/failing/100 fallback consistency/test.json new file mode 100644 index 0000000..a783d8c --- /dev/null +++ b/test cases/failing/100 fallback consistency/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/100 fallback consistency/meson.build:7:0: ERROR: Inconsistency: Subproject has overridden the dependency with another variable than 'dep2'" + } + ] +} diff --git a/test cases/failing/101 no native compiler/test.json b/test cases/failing/101 no native compiler/test.json new file mode 100644 index 0000000..c7b5d1c --- /dev/null +++ b/test cases/failing/101 no native compiler/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/101 no native compiler/meson.build:12:0: ERROR: No host machine compiler for \"main.c\"" + } + ] +} diff --git a/test cases/failing/102 subdir parse error/test.json b/test cases/failing/102 subdir parse error/test.json new file mode 100644 index 0000000..06fd4d3 --- /dev/null +++ b/test cases/failing/102 subdir parse error/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/102 subdir parse error/subdir/meson.build:1:0: ERROR: Plusassignment target must be an id." + } + ] +} diff --git a/test cases/failing/103 invalid option file/test.json b/test cases/failing/103 invalid option file/test.json new file mode 100644 index 0000000..20dbec3 --- /dev/null +++ b/test cases/failing/103 invalid option file/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/103 invalid option file/meson_options.txt:1:0: ERROR: lexer" + } + ] +} diff --git a/test cases/failing/104 no lang/test.json b/test cases/failing/104 no lang/test.json new file mode 100644 index 0000000..62999be --- /dev/null +++ b/test cases/failing/104 no lang/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/104 no lang/meson.build:2:0: ERROR: No host machine compiler for \"main.c\"" + } + ] +} diff --git a/test cases/failing/105 no glib-compile-resources/test.json b/test cases/failing/105 no glib-compile-resources/test.json new file mode 100644 index 0000000..67dc7e4 --- /dev/null +++ b/test cases/failing/105 no glib-compile-resources/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/105 no glib-compile-resources/meson.build:8:0: ERROR: Could not execute glib-compile-resources." + } + ] +} diff --git a/test cases/failing/11 object arithmetic/test.json b/test cases/failing/11 object arithmetic/test.json new file mode 100644 index 0000000..5339fac --- /dev/null +++ b/test cases/failing/11 object arithmetic/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": "test cases/failing/11 object arithmetic/meson\\.build:3:0: ERROR: Invalid use of addition: .*" + } + ] +} diff --git a/test cases/failing/12 string arithmetic/test.json b/test cases/failing/12 string arithmetic/test.json new file mode 100644 index 0000000..476f9bb --- /dev/null +++ b/test cases/failing/12 string arithmetic/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": "test cases/failing/12 string arithmetic/meson\\.build:3:0: ERROR: Invalid use of addition: .*" + } + ] +} diff --git a/test cases/failing/13 array arithmetic/test.json b/test cases/failing/13 array arithmetic/test.json new file mode 100644 index 0000000..55056ce --- /dev/null +++ b/test cases/failing/13 array arithmetic/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/13 array arithmetic/meson.build:3:0: ERROR: Multiplication works only with integers." + } + ] +} diff --git a/test cases/failing/14 invalid option name/test.json b/test cases/failing/14 invalid option name/test.json new file mode 100644 index 0000000..71e685d --- /dev/null +++ b/test cases/failing/14 invalid option name/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/14 invalid option name/meson_options.txt:1:0: ERROR: Option names can only contain letters, numbers or dashes." + } + ] +} diff --git a/test cases/failing/15 kwarg before arg/test.json b/test cases/failing/15 kwarg before arg/test.json new file mode 100644 index 0000000..c7f72c3 --- /dev/null +++ b/test cases/failing/15 kwarg before arg/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/15 kwarg before arg/meson.build:3:0: ERROR: All keyword arguments must be after positional arguments." + } + ] +} diff --git a/test cases/failing/16 extract from subproject/test.json b/test cases/failing/16 extract from subproject/test.json new file mode 100644 index 0000000..78d45a5 --- /dev/null +++ b/test cases/failing/16 extract from subproject/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/16 extract from subproject/meson.build:6:0: ERROR: Tried to extract objects from a subproject target." + } + ] +} diff --git a/test cases/failing/17 same target/test.json b/test cases/failing/17 same target/test.json new file mode 100644 index 0000000..0005ba4 --- /dev/null +++ b/test cases/failing/17 same target/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/17 same target/meson.build:4:0: ERROR: Tried to create target \"foo\", but a target of that name already exists." + } + ] +} diff --git a/test cases/failing/18 wrong plusassign/test.json b/test cases/failing/18 wrong plusassign/test.json new file mode 100644 index 0000000..c698f85 --- /dev/null +++ b/test cases/failing/18 wrong plusassign/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/18 wrong plusassign/meson.build:3:0: ERROR: Plusassignment target must be an id." + } + ] +} diff --git a/test cases/failing/19 target clash/meson.build b/test cases/failing/19 target clash/meson.build index ca09fb5..4fd0934 100644 --- a/test cases/failing/19 target clash/meson.build +++ b/test cases/failing/19 target clash/meson.build @@ -8,7 +8,7 @@ project('clash', 'c') # output location is redirected. if host_machine.system() == 'windows' or host_machine.system() == 'cygwin' - error('This is expected.') + error('MESON_SKIP_TEST test only works on platforms where executables have no suffix.') endif executable('clash', 'clash.c') diff --git a/test cases/failing/19 target clash/test.json b/test cases/failing/19 target clash/test.json new file mode 100644 index 0000000..d22b894 --- /dev/null +++ b/test cases/failing/19 target clash/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "ERROR: Multiple producers for Ninja target \"clash\". Please rename your targets." + } + ] +} diff --git a/test cases/failing/2 missing file/test.json b/test cases/failing/2 missing file/test.json new file mode 100644 index 0000000..b95b8b0 --- /dev/null +++ b/test cases/failing/2 missing file/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/2 missing file/meson.build:3:0: ERROR: File missing.c does not exist." + } + ] +} diff --git a/test cases/failing/20 version/test.json b/test cases/failing/20 version/test.json new file mode 100644 index 0000000..f330624 --- /dev/null +++ b/test cases/failing/20 version/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": "test cases/failing/20 version/meson\\.build:1:0: ERROR: Meson version is .* but project requires >100\\.0\\.0" + } + ] +} diff --git a/test cases/failing/21 subver/test.json b/test cases/failing/21 subver/test.json new file mode 100644 index 0000000..f8cfd3a --- /dev/null +++ b/test cases/failing/21 subver/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/21 subver/meson.build:3:0: ERROR: Subproject foo version is 1.0.0 but >1.0.0 required." + } + ] +} diff --git a/test cases/failing/22 assert/test.json b/test cases/failing/22 assert/test.json new file mode 100644 index 0000000..edae999 --- /dev/null +++ b/test cases/failing/22 assert/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/22 assert/meson.build:3:0: ERROR: Assert failed: I am fail." + } + ] +} diff --git a/test cases/failing/23 rel testdir/test.json b/test cases/failing/23 rel testdir/test.json new file mode 100644 index 0000000..ba983ab --- /dev/null +++ b/test cases/failing/23 rel testdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/23 rel testdir/meson.build:4:0: ERROR: Workdir keyword argument must be an absolute path." + } + ] +} diff --git a/test cases/failing/24 int conversion/test.json b/test cases/failing/24 int conversion/test.json new file mode 100644 index 0000000..e749928 --- /dev/null +++ b/test cases/failing/24 int conversion/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/24 int conversion/meson.build:3:13: ERROR: String 'notanumber' cannot be converted to int" + } + ] +} diff --git a/test cases/failing/25 badlang/test.json b/test cases/failing/25 badlang/test.json new file mode 100644 index 0000000..0b23fd7 --- /dev/null +++ b/test cases/failing/25 badlang/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/25 badlang/meson.build:3:0: ERROR: Tried to use unknown language \"nonexisting\"." + } + ] +} diff --git a/test cases/failing/26 output subdir/test.json b/test cases/failing/26 output subdir/test.json new file mode 100644 index 0000000..796468d --- /dev/null +++ b/test cases/failing/26 output subdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/26 output subdir/meson.build:3:0: ERROR: Output file name must not contain a subdirectory." + } + ] +} diff --git a/test cases/failing/27 noprog use/test.json b/test cases/failing/27 noprog use/test.json new file mode 100644 index 0000000..b84562e --- /dev/null +++ b/test cases/failing/27 noprog use/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/27 noprog use/meson.build:5:0: ERROR: Tried to use not-found external program in \"command\"" + } + ] +} diff --git a/test cases/failing/28 no crossprop/test.json b/test cases/failing/28 no crossprop/test.json new file mode 100644 index 0000000..a186a68 --- /dev/null +++ b/test cases/failing/28 no crossprop/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/28 no crossprop/meson.build:3:0: ERROR: Unknown cross property: nonexisting." + } + ] +} diff --git a/test cases/failing/29 nested ternary/test.json b/test cases/failing/29 nested ternary/test.json new file mode 100644 index 0000000..ba05013 --- /dev/null +++ b/test cases/failing/29 nested ternary/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/29 nested ternary/meson.build:3:12: ERROR: Nested ternary operators are not allowed." + } + ] +} diff --git a/test cases/failing/3 missing subdir/test.json b/test cases/failing/3 missing subdir/test.json new file mode 100644 index 0000000..562de25 --- /dev/null +++ b/test cases/failing/3 missing subdir/test.json @@ -0,0 +1,9 @@ +{ + "stdout": [ + { + "comment": "'missing/meson.build' gets transformed with os.path.sep separators", + "match": "re", + "line": "test cases/failing/3 missing subdir/meson\\.build:3:0: ERROR: Non\\-existent build file 'missing[\\\\/]meson\\.build'" + } + ] +} diff --git a/test cases/failing/30 invalid man extension/test.json b/test cases/failing/30 invalid man extension/test.json new file mode 100644 index 0000000..3f77a04 --- /dev/null +++ b/test cases/failing/30 invalid man extension/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/30 invalid man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 8" + } + ] +} diff --git a/test cases/failing/31 no man extension/test.json b/test cases/failing/31 no man extension/test.json new file mode 100644 index 0000000..6e1f542 --- /dev/null +++ b/test cases/failing/31 no man extension/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/31 no man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 8" + } + ] +} diff --git a/test cases/failing/32 exe static shared/meson.build b/test cases/failing/32 exe static shared/meson.build index b102764..2ae5125 100644 --- a/test cases/failing/32 exe static shared/meson.build +++ b/test cases/failing/32 exe static shared/meson.build @@ -2,7 +2,7 @@ project('statchain', 'c') host_system = host_machine.system() if host_system == 'windows' or host_system == 'darwin' - error('Test only fails on Linux and BSD') + error('MESON_SKIP_TEST test only fails on Linux and BSD') endif statlib = static_library('stat', 'stat.c', pic : false) diff --git a/test cases/failing/32 exe static shared/test.json b/test cases/failing/32 exe static shared/test.json new file mode 100644 index 0000000..51d3804 --- /dev/null +++ b/test cases/failing/32 exe static shared/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/32 exe static shared/meson.build:9:0: ERROR: Can't link non-PIC static library 'stat' into shared library 'shr2'. Use the 'pic' option to static_library to build with PIC." + } + ] +} diff --git a/test cases/failing/33 non-root subproject/test.json b/test cases/failing/33 non-root subproject/test.json new file mode 100644 index 0000000..a14cece --- /dev/null +++ b/test cases/failing/33 non-root subproject/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/33 non-root subproject/some/meson.build:1:0: ERROR: Subproject directory not found and someproj.wrap file not found" + } + ] +} diff --git a/test cases/failing/34 dependency not-required then required/test.json b/test cases/failing/34 dependency not-required then required/test.json new file mode 100644 index 0000000..3cf35f5 --- /dev/null +++ b/test cases/failing/34 dependency not-required then required/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": ".*/meson\\.build:4:0: ERROR: (Pkg-config binary for machine MachineChoice\\.HOST not found\\. Giving up\\.|Dependency \"foo\\-bar\\-xyz\\-12\\.3\" not found, tried .*)" + } + ] +} diff --git a/test cases/failing/35 project argument after target/test.json b/test cases/failing/35 project argument after target/test.json new file mode 100644 index 0000000..f5efd9b --- /dev/null +++ b/test cases/failing/35 project argument after target/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/35 project argument after target/meson.build:7:0: ERROR: Tried to use 'add_project_arguments' after a build target has been declared." + } + ] +} diff --git a/test cases/failing/36 pkgconfig dependency impossible conditions/meson.build b/test cases/failing/36 pkgconfig dependency impossible conditions/meson.build index 54d434c..874b581 100644 --- a/test cases/failing/36 pkgconfig dependency impossible conditions/meson.build +++ b/test cases/failing/36 pkgconfig dependency impossible conditions/meson.build @@ -1,3 +1,7 @@ project('impossible-dep-test', 'c', version : '1.0') +if not dependency('zlib', required: false).found() + error('MESON_SKIP_TEST test requires zlib') +endif + dependency('zlib', version : ['>=1.0', '<1.0']) diff --git a/test cases/failing/36 pkgconfig dependency impossible conditions/test.json b/test cases/failing/36 pkgconfig dependency impossible conditions/test.json new file mode 100644 index 0000000..2ce62ac --- /dev/null +++ b/test cases/failing/36 pkgconfig dependency impossible conditions/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/36 pkgconfig dependency impossible conditions/meson.build:7:0: ERROR: Dependency 'zlib' was already checked and was not found" + } + ] +} diff --git a/test cases/failing/37 has function external dependency/test.json b/test cases/failing/37 has function external dependency/test.json new file mode 100644 index 0000000..81d6f91 --- /dev/null +++ b/test cases/failing/37 has function external dependency/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/37 has function external dependency/meson.build:8:3: ERROR: Dependencies must be external dependencies" + } + ] +} diff --git a/test cases/failing/38 libdir must be inside prefix/test.json b/test cases/failing/38 libdir must be inside prefix/test.json index 1cd893c..d9256d1 100644 --- a/test cases/failing/38 libdir must be inside prefix/test.json +++ b/test cases/failing/38 libdir must be inside prefix/test.json @@ -1,3 +1,10 @@ { - "do_not_set_opts": ["libdir"] + "do_not_set_opts": [ + "libdir" + ], + "stdout": [ + { + "line": "test cases/failing/38 libdir must be inside prefix/meson.build:1:0: ERROR: The value of the 'libdir' option is '/opt/lib' which must be a subdir of the prefix '/usr'." + } + ] } diff --git a/test cases/failing/39 prefix absolute/test.json b/test cases/failing/39 prefix absolute/test.json index 4e0f6cd..2770243 100644 --- a/test cases/failing/39 prefix absolute/test.json +++ b/test cases/failing/39 prefix absolute/test.json @@ -1,3 +1,11 @@ { - "do_not_set_opts": ["prefix"] + "do_not_set_opts": [ + "prefix" + ], + "stdout": [ + { + "comment": "literal 'some/path/notabs' appears in output, irrespective of os.path.sep, as that's the prefix", + "line": "test cases/failing/39 prefix absolute/meson.build:1:0: ERROR: prefix value 'some/path/notabs' must be an absolute path" + } + ] } diff --git a/test cases/failing/4 missing meson.build/test.json b/test cases/failing/4 missing meson.build/test.json new file mode 100644 index 0000000..3857090 --- /dev/null +++ b/test cases/failing/4 missing meson.build/test.json @@ -0,0 +1,9 @@ +{ + "stdout": [ + { + "match": "re", + "comment": "'subdir/meson.build' gets transformed with os.path.sep separators", + "line": "test cases/failing/4 missing meson\\.build/meson\\.build:3:0: ERROR: Non\\-existent build file 'subdir[\\\\/]meson\\.build'" + } + ] +} diff --git a/test cases/failing/40 kwarg assign/test.json b/test cases/failing/40 kwarg assign/test.json new file mode 100644 index 0000000..671eb3f --- /dev/null +++ b/test cases/failing/40 kwarg assign/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/40 kwarg assign/meson.build:3:0: ERROR: Tried to assign values inside an argument list." + } + ] +} diff --git a/test cases/failing/41 custom target plainname many inputs/test.json b/test cases/failing/41 custom target plainname many inputs/test.json new file mode 100644 index 0000000..8c15cda --- /dev/null +++ b/test cases/failing/41 custom target plainname many inputs/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/41 custom target plainname many inputs/meson.build:5:0: ERROR: Output cannot contain @PLAINNAME@ or @BASENAME@ when there is more than one input (we can't know which to use)" + } + ] +} diff --git a/test cases/failing/42 custom target outputs not matching install_dirs/meson.build b/test cases/failing/42 custom target outputs not matching install_dirs/meson.build index 45bd7b3..765e237 100644 --- a/test cases/failing/42 custom target outputs not matching install_dirs/meson.build +++ b/test cases/failing/42 custom target outputs not matching install_dirs/meson.build @@ -3,7 +3,7 @@ project('outputs not matching install_dirs', 'c') gen = find_program('generator.py') if meson.backend() != 'ninja' - error('Failing manually, test is only for the ninja backend') + error('MESON_SKIP_TEST test is only for the ninja backend') endif custom_target('too-few-install-dirs', diff --git a/test cases/failing/42 custom target outputs not matching install_dirs/test.json b/test cases/failing/42 custom target outputs not matching install_dirs/test.json index e59cb9f..f9e2ba7 100644 --- a/test cases/failing/42 custom target outputs not matching install_dirs/test.json +++ b/test cases/failing/42 custom target outputs not matching install_dirs/test.json @@ -1,10 +1,33 @@ { "installed": [ - {"type": "file", "file": "usr/include/diff.h"}, - {"type": "file", "file": "usr/include/first.h"}, - {"type": "file", "file": "usr/bin/diff.sh"}, - {"type": "file", "file": "usr/bin/second.sh"}, - {"type": "file", "file": "opt/same.h"}, - {"type": "file", "file": "opt/same.sh"} + { + "type": "file", + "file": "usr/include/diff.h" + }, + { + "type": "file", + "file": "usr/include/first.h" + }, + { + "type": "file", + "file": "usr/bin/diff.sh" + }, + { + "type": "file", + "file": "usr/bin/second.sh" + }, + { + "type": "file", + "file": "opt/same.h" + }, + { + "type": "file", + "file": "opt/same.sh" + } + ], + "stdout": [ + { + "line": "ERROR: Target 'too-few-install-dirs' has 3 outputs: ['toofew.h', 'toofew.c', 'toofew.sh'], but only 2 \"install_dir\"s were found." + } ] } diff --git a/test cases/failing/43 project name colon/test.json b/test cases/failing/43 project name colon/test.json new file mode 100644 index 0000000..7a55574 --- /dev/null +++ b/test cases/failing/43 project name colon/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/43 project name colon/meson.build:1:0: ERROR: Project name 'name with :' must not contain ':'" + } + ] +} diff --git a/test cases/failing/44 abs subdir/test.json b/test cases/failing/44 abs subdir/test.json new file mode 100644 index 0000000..0aa56f6 --- /dev/null +++ b/test cases/failing/44 abs subdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/44 abs subdir/meson.build:5:0: ERROR: Subdir argument must be a relative path." + } + ] +} diff --git a/test cases/failing/45 abspath to srcdir/test.json b/test cases/failing/45 abspath to srcdir/test.json new file mode 100644 index 0000000..b6a87fe --- /dev/null +++ b/test cases/failing/45 abspath to srcdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/45 abspath to srcdir/meson.build:3:0: ERROR: Tried to form an absolute path to a source dir. You should not do that but use relative paths instead." + } + ] +} diff --git a/test cases/failing/46 pkgconfig variables reserved/test.json b/test cases/failing/46 pkgconfig variables reserved/test.json new file mode 100644 index 0000000..b92ee17 --- /dev/null +++ b/test cases/failing/46 pkgconfig variables reserved/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/46 pkgconfig variables reserved/meson.build:8:5: ERROR: Variable \"prefix\" is reserved" + } + ] +} diff --git a/test cases/failing/47 pkgconfig variables zero length/test.json b/test cases/failing/47 pkgconfig variables zero length/test.json new file mode 100644 index 0000000..097fee1 --- /dev/null +++ b/test cases/failing/47 pkgconfig variables zero length/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/47 pkgconfig variables zero length/meson.build:8:5: ERROR: Invalid variable \"=value\". Variables must be in 'name=value' format" + } + ] +} diff --git a/test cases/failing/48 pkgconfig variables zero length value/test.json b/test cases/failing/48 pkgconfig variables zero length value/test.json new file mode 100644 index 0000000..50a35ce --- /dev/null +++ b/test cases/failing/48 pkgconfig variables zero length value/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/48 pkgconfig variables zero length value/meson.build:8:5: ERROR: Invalid variable \"key=\". Variables must be in 'name=value' format" + } + ] +} diff --git a/test cases/failing/49 pkgconfig variables not key value/test.json b/test cases/failing/49 pkgconfig variables not key value/test.json new file mode 100644 index 0000000..cf07e62 --- /dev/null +++ b/test cases/failing/49 pkgconfig variables not key value/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/49 pkgconfig variables not key value/meson.build:8:5: ERROR: Invalid variable \"this_should_be_key_value\". Variables must be in 'name=value' format" + } + ] +} diff --git a/test cases/failing/5 misplaced option/test.json b/test cases/failing/5 misplaced option/test.json new file mode 100644 index 0000000..12afdf0 --- /dev/null +++ b/test cases/failing/5 misplaced option/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/5 misplaced option/meson.build:3:0: ERROR: Tried to call option() in build description file. All options must be in the option file." + } + ] +} diff --git a/test cases/failing/50 executable comparison/test.json b/test cases/failing/50 executable comparison/test.json new file mode 100644 index 0000000..585b382 --- /dev/null +++ b/test cases/failing/50 executable comparison/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/50 executable comparison/meson.build:6:0: ERROR: exe1 can only be compared for equality." + } + ] +} diff --git a/test cases/failing/51 inconsistent comparison/test.json b/test cases/failing/51 inconsistent comparison/test.json new file mode 100644 index 0000000..5867f0a --- /dev/null +++ b/test cases/failing/51 inconsistent comparison/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/51 inconsistent comparison/meson.build:5:0: ERROR: Values of different types (list, str) cannot be compared using <." + } + ] +} diff --git a/test cases/failing/52 slashname/test.json b/test cases/failing/52 slashname/test.json new file mode 100644 index 0000000..180400a --- /dev/null +++ b/test cases/failing/52 slashname/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/52 slashname/meson.build:11:0: ERROR: Problem encountered: Re-enable me once slash in name is finally prohibited." + } + ] +} diff --git a/test cases/failing/53 reserved meson prefix/test.json b/test cases/failing/53 reserved meson prefix/test.json new file mode 100644 index 0000000..502d96a --- /dev/null +++ b/test cases/failing/53 reserved meson prefix/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/53 reserved meson prefix/meson.build:3:0: ERROR: The \"meson-\" prefix is reserved and cannot be used for top-level subdir()." + } + ] +} diff --git a/test cases/failing/54 wrong shared crate type/meson.build b/test cases/failing/54 wrong shared crate type/meson.build index 69ac3da..b9fcad4 100644 --- a/test cases/failing/54 wrong shared crate type/meson.build +++ b/test cases/failing/54 wrong shared crate type/meson.build @@ -1,3 +1,7 @@ -project('test', 'rust') +project('test') + +if not add_languages('rust', required: false) + error('MESON_SKIP_TEST test requires rust compiler') +endif shared_library('test', 'foo.rs', rust_crate_type : 'staticlib') diff --git a/test cases/failing/54 wrong shared crate type/test.json b/test cases/failing/54 wrong shared crate type/test.json new file mode 100644 index 0000000..5cced6f --- /dev/null +++ b/test cases/failing/54 wrong shared crate type/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/54 wrong shared crate type/meson.build:7:0: ERROR: Crate type \"staticlib\" invalid for dynamic libraries; must be \"dylib\" or \"cdylib\"" + } + ] +} diff --git a/test cases/failing/55 wrong static crate type/meson.build b/test cases/failing/55 wrong static crate type/meson.build index c094613..109907f 100644 --- a/test cases/failing/55 wrong static crate type/meson.build +++ b/test cases/failing/55 wrong static crate type/meson.build @@ -1,3 +1,7 @@ -project('test', 'rust') +project('test') + +if not add_languages('rust', required: false) + error('MESON_SKIP_TEST test requires rust compiler') +endif static_library('test', 'foo.rs', rust_crate_type : 'cdylib') diff --git a/test cases/failing/55 wrong static crate type/test.json b/test cases/failing/55 wrong static crate type/test.json new file mode 100644 index 0000000..7073f7b --- /dev/null +++ b/test cases/failing/55 wrong static crate type/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/55 wrong static crate type/meson.build:7:0: ERROR: Crate type \"cdylib\" invalid for static libraries; must be \"rlib\" or \"staticlib\"" + } + ] +} diff --git a/test cases/failing/56 or on new line/test.json b/test cases/failing/56 or on new line/test.json new file mode 100644 index 0000000..c55cee6 --- /dev/null +++ b/test cases/failing/56 or on new line/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/56 or on new line/meson.build:4:8: ERROR: Invalid or clause." + } + ] +} diff --git a/test cases/failing/57 kwarg in module/test.json b/test cases/failing/57 kwarg in module/test.json new file mode 100644 index 0000000..cafb3ab --- /dev/null +++ b/test cases/failing/57 kwarg in module/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/57 kwarg in module/meson.build:3:0: ERROR: Function does not take keyword arguments." + } + ] +} diff --git a/test cases/failing/58 link with executable/test.json b/test cases/failing/58 link with executable/test.json new file mode 100644 index 0000000..d3975c1 --- /dev/null +++ b/test cases/failing/58 link with executable/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/58 link with executable/meson.build:4:0: ERROR: Link target 'prog' is not linkable." + } + ] +} diff --git a/test cases/failing/59 assign custom target index/test.json b/test cases/failing/59 assign custom target index/test.json new file mode 100644 index 0000000..07ecb91 --- /dev/null +++ b/test cases/failing/59 assign custom target index/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/59 assign custom target index/meson.build:24:0: ERROR: Assignment target must be an id." + } + ] +} diff --git a/test cases/failing/6 missing incdir/test.json b/test cases/failing/6 missing incdir/test.json new file mode 100644 index 0000000..172d8a9 --- /dev/null +++ b/test cases/failing/6 missing incdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/6 missing incdir/meson.build:3:0: ERROR: Include dir nosuchdir does not exist." + } + ] +} diff --git a/test cases/failing/60 getoption prefix/test.json b/test cases/failing/60 getoption prefix/test.json new file mode 100644 index 0000000..03bf419 --- /dev/null +++ b/test cases/failing/60 getoption prefix/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/60 getoption prefix/meson.build:5:0: ERROR: Having a colon in option name is forbidden, projects are not allowed to directly access options of other subprojects." + } + ] +} diff --git a/test cases/failing/61 bad option argument/test.json b/test cases/failing/61 bad option argument/test.json new file mode 100644 index 0000000..4002005 --- /dev/null +++ b/test cases/failing/61 bad option argument/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/61 bad option argument/meson_options.txt:1:0: ERROR: Invalid kwargs for option \"name\": \"vaule\"" + } + ] +} diff --git a/test cases/failing/62 subproj filegrab/test.json b/test cases/failing/62 subproj filegrab/test.json new file mode 100644 index 0000000..dd0d7bb --- /dev/null +++ b/test cases/failing/62 subproj filegrab/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/62 subproj filegrab/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file prog.c from a different subproject." + } + ] +} diff --git a/test cases/failing/63 grab subproj/test.json b/test cases/failing/63 grab subproj/test.json new file mode 100644 index 0000000..8147905 --- /dev/null +++ b/test cases/failing/63 grab subproj/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/63 grab subproj/meson.build:7:0: ERROR: Sandbox violation: Tried to grab file sub.c from a different subproject." + } + ] +} diff --git a/test cases/failing/64 grab sibling/test.json b/test cases/failing/64 grab sibling/test.json new file mode 100644 index 0000000..1604d47 --- /dev/null +++ b/test cases/failing/64 grab sibling/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/64 grab sibling/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file sneaky.c from a different subproject." + } + ] +} diff --git a/test cases/failing/65 string as link target/test.json b/test cases/failing/65 string as link target/test.json new file mode 100644 index 0000000..e212482 --- /dev/null +++ b/test cases/failing/65 string as link target/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/65 string as link target/meson.build:2:0: ERROR: '' is not a target." + } + ] +} diff --git a/test cases/failing/66 dependency not-found and required/test.json b/test cases/failing/66 dependency not-found and required/test.json new file mode 100644 index 0000000..5b13316 --- /dev/null +++ b/test cases/failing/66 dependency not-found and required/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/66 dependency not-found and required/meson.build:2:0: ERROR: Dependency is both required and not-found" + } + ] +} diff --git a/test cases/failing/67 subproj different versions/test.json b/test cases/failing/67 subproj different versions/test.json new file mode 100644 index 0000000..d16daf9 --- /dev/null +++ b/test cases/failing/67 subproj different versions/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/67 subproj different versions/subprojects/b/meson.build:3:0: ERROR: Dependency 'c' was already checked and was not found" + } + ] +} diff --git a/test cases/failing/68 wrong boost module/meson.build b/test cases/failing/68 wrong boost module/meson.build index 7fb3a40..937e587 100644 --- a/test cases/failing/68 wrong boost module/meson.build +++ b/test cases/failing/68 wrong boost module/meson.build @@ -1,5 +1,9 @@ project('boosttest', 'cpp', default_options : ['cpp_std=c++11']) +if not dependency('boost', required: false).found() + error('MESON_SKIP_TEST test requires boost') +endif + # abc doesn't exist linkdep = dependency('boost', modules : ['thread', 'system', 'test', 'abc']) diff --git a/test cases/failing/68 wrong boost module/test.json b/test cases/failing/68 wrong boost module/test.json new file mode 100644 index 0000000..9ef1b0f --- /dev/null +++ b/test cases/failing/68 wrong boost module/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/68 wrong boost module/meson.build:9:0: ERROR: Dependency \"boost\" not found" + } + ] +} diff --git a/test cases/failing/69 install_data rename bad size/test.json b/test cases/failing/69 install_data rename bad size/test.json new file mode 100644 index 0000000..1329fec --- /dev/null +++ b/test cases/failing/69 install_data rename bad size/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/69 install_data rename bad size/meson.build:3:0: ERROR: Size of rename argument is different from number of sources" + } + ] +} diff --git a/test cases/failing/7 go to subproject/test.json b/test cases/failing/7 go to subproject/test.json new file mode 100644 index 0000000..c254757 --- /dev/null +++ b/test cases/failing/7 go to subproject/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/7 go to subproject/meson.build:3:0: ERROR: Must not go into subprojects dir with subdir(), use subproject() instead." + } + ] +} diff --git a/test cases/failing/70 skip only subdir/test.json b/test cases/failing/70 skip only subdir/test.json new file mode 100644 index 0000000..3b40b66 --- /dev/null +++ b/test cases/failing/70 skip only subdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/70 skip only subdir/meson.build:8:0: ERROR: File main.cpp does not exist." + } + ] +} diff --git a/test cases/failing/71 dual override/test.json b/test cases/failing/71 dual override/test.json new file mode 100644 index 0000000..66409e6 --- /dev/null +++ b/test cases/failing/71 dual override/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/71 dual override/meson.build:5:6: ERROR: Tried to override executable \"override\" which has already been overridden." + } + ] +} diff --git a/test cases/failing/72 override used/test.json b/test cases/failing/72 override used/test.json new file mode 100644 index 0000000..29a58f1 --- /dev/null +++ b/test cases/failing/72 override used/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/72 override used/meson.build:5:6: ERROR: Tried to override finding of executable \"something.py\" which has already been found." + } + ] +} diff --git a/test cases/failing/73 run_command unclean exit/test.json b/test cases/failing/73 run_command unclean exit/test.json new file mode 100644 index 0000000..beda187 --- /dev/null +++ b/test cases/failing/73 run_command unclean exit/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": "test cases/failing/73 run_command unclean exit/meson\\.build:4:0: ERROR: Command \".*[\\\\/]test cases[\\\\/]failing[\\\\/]73 run_command unclean exit[\\\\/]\\.[\\\\/]returncode\\.py 1\" failed with status 1\\." + } + ] +} diff --git a/test cases/failing/74 int literal leading zero/test.json b/test cases/failing/74 int literal leading zero/test.json new file mode 100644 index 0000000..78a735e --- /dev/null +++ b/test cases/failing/74 int literal leading zero/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "comment": "this error message is not very informative", + "line": "test cases/failing/74 int literal leading zero/meson.build:5:13: ERROR: Expecting eof got number." + } + ] +} diff --git a/test cases/failing/75 configuration immutable/test.json b/test cases/failing/75 configuration immutable/test.json new file mode 100644 index 0000000..3365aae --- /dev/null +++ b/test cases/failing/75 configuration immutable/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/75 configuration immutable/meson.build:12:16: ERROR: Can not set values on configuration object that has been used." + } + ] +} diff --git a/test cases/failing/76 link with shared module on osx/meson.build b/test cases/failing/76 link with shared module on osx/meson.build index 2c714f9..bf18b36 100644 --- a/test cases/failing/76 link with shared module on osx/meson.build +++ b/test cases/failing/76 link with shared module on osx/meson.build @@ -1,7 +1,7 @@ project('link with shared module', 'c') if host_machine.system() != 'darwin' - error('Test only fails on OSX') + error('MESON_SKIP_TEST test only fails on OSX') endif m = shared_module('mymodule', 'module.c') diff --git a/test cases/failing/76 link with shared module on osx/test.json b/test cases/failing/76 link with shared module on osx/test.json new file mode 100644 index 0000000..4e2856f --- /dev/null +++ b/test cases/failing/76 link with shared module on osx/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/76 link with shared module on osx/meson.build:8:0: ERROR: target links against shared modules." + } + ] +} diff --git a/test cases/failing/77 non ascii in ascii encoded configure file/test.json b/test cases/failing/77 non ascii in ascii encoded configure file/test.json new file mode 100644 index 0000000..e35b95b --- /dev/null +++ b/test cases/failing/77 non ascii in ascii encoded configure file/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": "test cases/failing/77 non ascii in ascii encoded configure file/meson\\.build:5:0: ERROR: Could not write output file .*[\\\\/]config9\\.h: 'ascii' codec can't encode character '\\\\u0434' in position 17: ordinal not in range\\(128\\)" + } + ] +} diff --git a/test cases/failing/78 subproj dependency not-found and required/test.json b/test cases/failing/78 subproj dependency not-found and required/test.json new file mode 100644 index 0000000..534b4f4 --- /dev/null +++ b/test cases/failing/78 subproj dependency not-found and required/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/78 subproj dependency not-found and required/meson.build:2:0: ERROR: Subproject directory not found and missing.wrap file not found" + } + ] +} diff --git a/test cases/failing/79 unfound run/test.json b/test cases/failing/79 unfound run/test.json new file mode 100644 index 0000000..6baafc0 --- /dev/null +++ b/test cases/failing/79 unfound run/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/79 unfound run/meson.build:4:0: ERROR: Tried to use non-existing executable 'nonexisting_prog'" + } + ] +} diff --git a/test cases/failing/8 recursive/test.json b/test cases/failing/8 recursive/test.json new file mode 100644 index 0000000..b4c964c --- /dev/null +++ b/test cases/failing/8 recursive/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/8 recursive/subprojects/b/meson.build:3:0: ERROR: Recursive include of subprojects: a => b => a." + } + ] +} diff --git a/test cases/failing/80 framework dependency with version/meson.build b/test cases/failing/80 framework dependency with version/meson.build index 1ead388..b7e04ba 100644 --- a/test cases/failing/80 framework dependency with version/meson.build +++ b/test cases/failing/80 framework dependency with version/meson.build @@ -1,4 +1,8 @@ project('framework dependency with version', 'c') + +if host_machine.system() != 'darwin' + error('MESON_SKIP_TEST test only applicable on darwin') +endif + # do individual frameworks have a meaningful version to test? And multiple frameworks might be listed... -# otherwise we're not on OSX and this will definitely fail dep = dependency('appleframeworks', modules: 'foundation', version: '>0') diff --git a/test cases/failing/80 framework dependency with version/test.json b/test cases/failing/80 framework dependency with version/test.json new file mode 100644 index 0000000..5cbc129 --- /dev/null +++ b/test cases/failing/80 framework dependency with version/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/80 framework dependency with version/meson.build:8:0: ERROR: Unknown version of dependency 'appleframeworks', but need ['>0']." + } + ] +} diff --git a/test cases/failing/81 override exe config/test.json b/test cases/failing/81 override exe config/test.json new file mode 100644 index 0000000..f19785b --- /dev/null +++ b/test cases/failing/81 override exe config/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/81 override exe config/meson.build:6:0: ERROR: Program 'bar' was overridden with the compiled executable 'foo' and therefore cannot be used during configuration" + } + ] +} diff --git a/test cases/failing/82 gl dependency with version/meson.build b/test cases/failing/82 gl dependency with version/meson.build index 3014d43..0127093 100644 --- a/test cases/failing/82 gl dependency with version/meson.build +++ b/test cases/failing/82 gl dependency with version/meson.build @@ -2,7 +2,7 @@ project('gl dependency with version', 'c') host_system = host_machine.system() if host_system != 'windows' and host_system != 'darwin' - error('Test only fails on Windows and OSX') + error('MESON_SKIP_TEST: test only fails on Windows and OSX') endif # gl dependency found via system method doesn't have a meaningful version to check diff --git a/test cases/failing/82 gl dependency with version/test.json b/test cases/failing/82 gl dependency with version/test.json new file mode 100644 index 0000000..2c63a2c --- /dev/null +++ b/test cases/failing/82 gl dependency with version/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/82 gl dependency with version/meson.build:9:0: ERROR: Unknown version of dependency 'gl', but need ['>0']." + } + ] +} diff --git a/test cases/failing/83 threads dependency with version/test.json b/test cases/failing/83 threads dependency with version/test.json new file mode 100644 index 0000000..b131be4 --- /dev/null +++ b/test cases/failing/83 threads dependency with version/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/83 threads dependency with version/meson.build:3:0: ERROR: Unknown version of dependency 'threads', but need ['>0']." + } + ] +} diff --git a/test cases/failing/84 gtest dependency with version/meson.build b/test cases/failing/84 gtest dependency with version/meson.build index 3d90994..b43a047 100644 --- a/test cases/failing/84 gtest dependency with version/meson.build +++ b/test cases/failing/84 gtest dependency with version/meson.build @@ -1,3 +1,8 @@ project('gtest dependency with version', ['c', 'cpp']) + +if not dependency('gtest', method: 'system', required: false).found() + error('MESON_SKIP_TEST test requires gtest') +endif + # discovering gtest version is not yet implemented dep = dependency('gtest', method: 'system', version: '>0') diff --git a/test cases/failing/84 gtest dependency with version/test.json b/test cases/failing/84 gtest dependency with version/test.json new file mode 100644 index 0000000..e1bbcac --- /dev/null +++ b/test cases/failing/84 gtest dependency with version/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/84 gtest dependency with version/meson.build:8:0: ERROR: Dependency 'gtest' was already checked and was not found" + } + ] +} diff --git a/test cases/failing/85 dub libray/meson.build b/test cases/failing/85 dub libray/meson.build index 5b0ccac..306d5b3 100644 --- a/test cases/failing/85 dub libray/meson.build +++ b/test cases/failing/85 dub libray/meson.build @@ -1,3 +1,11 @@ -project('dub', 'd') +project('dub') + +if not add_languages('d', required: false) + error('MESON_SKIP_TEST test requires D compiler') +endif + +if not find_program('dub', required: false).found() + error('MESON_SKIP_TEST test requires dub') +endif dependency('dubtestproject', method: 'dub') # Not library (none) diff --git a/test cases/failing/85 dub libray/test.json b/test cases/failing/85 dub libray/test.json new file mode 100644 index 0000000..a8b3e28 --- /dev/null +++ b/test cases/failing/85 dub libray/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/85 dub libray/meson.build:11:0: ERROR: Dependency \"dubtestproject\" not found" + } + ] +} diff --git a/test cases/failing/86 dub executable/meson.build b/test cases/failing/86 dub executable/meson.build index 63fd631..9a134ea 100644 --- a/test cases/failing/86 dub executable/meson.build +++ b/test cases/failing/86 dub executable/meson.build @@ -1,3 +1,11 @@ -project('dub', 'd') +project('dub') + +if not add_languages('d', required: false) + error('MESON_SKIP_TEST test requires D compiler') +endif + +if not find_program('dub', required: false).found() + error('MESON_SKIP_TEST test requires dub') +endif dependency('dubtestproject:test1', method: 'dub') # Not library (executable) diff --git a/test cases/failing/86 dub executable/test.json b/test cases/failing/86 dub executable/test.json new file mode 100644 index 0000000..f9944af --- /dev/null +++ b/test cases/failing/86 dub executable/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/86 dub executable/meson.build:11:0: ERROR: Dependency \"dubtestproject:test1\" not found" + } + ] +} diff --git a/test cases/failing/87 dub compiler/meson.build b/test cases/failing/87 dub compiler/meson.build index c93ccbc..36f1849 100644 --- a/test cases/failing/87 dub compiler/meson.build +++ b/test cases/failing/87 dub compiler/meson.build @@ -1,4 +1,8 @@ -project('dub', 'd') +project('dub') + +if not add_languages('d', required: false) + error('MESON_SKIP_TEST test requires D compiler') +endif if meson.get_compiler('d').get_id() == 'dmd' if host_machine.system() == 'windows' or host_machine.system() == 'cygwin' @@ -6,4 +10,8 @@ if meson.get_compiler('d').get_id() == 'dmd' endif endif +if not find_program('dub', required: false).found() + error('MESON_SKIP_TEST test requires dub') +endif + dependency('dubtestproject:test2', method: 'dub') # Compiler mismatch diff --git a/test cases/failing/87 dub compiler/test.json b/test cases/failing/87 dub compiler/test.json index acb7da8..f28312f 100644 --- a/test cases/failing/87 dub compiler/test.json +++ b/test cases/failing/87 dub compiler/test.json @@ -2,8 +2,18 @@ "matrix": { "options": { "warning_level": [ - { "val": "1", "skip_on_env": [ "SINGLE_DUB_COMPILER" ] } + { + "val": "1", + "skip_on_env": [ + "SINGLE_DUB_COMPILER" + ] + } ] } - } + }, + "stdout": [ + { + "line": "test cases/failing/87 dub compiler/meson.build:17:0: ERROR: Dependency \"dubtestproject:test2\" not found" + } + ] } diff --git a/test cases/failing/88 subproj not-found dep/test.json b/test cases/failing/88 subproj not-found dep/test.json new file mode 100644 index 0000000..a1c4231 --- /dev/null +++ b/test cases/failing/88 subproj not-found dep/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/88 subproj not-found dep/meson.build:2:0: ERROR: Could not find dependency notfound_dep in subproject somesubproj" + } + ] +} diff --git a/test cases/failing/89 invalid configure file/test.json b/test cases/failing/89 invalid configure file/test.json new file mode 100644 index 0000000..921ce61 --- /dev/null +++ b/test cases/failing/89 invalid configure file/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/89 invalid configure file/meson.build:3:0: ERROR: \"install_dir\" must be specified when \"install\" in a configure_file is true" + } + ] +} diff --git a/test cases/failing/9 missing extra file/test.json b/test cases/failing/9 missing extra file/test.json new file mode 100644 index 0000000..188b6a6 --- /dev/null +++ b/test cases/failing/9 missing extra file/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/9 missing extra file/meson.build:3:0: ERROR: File missing.txt does not exist." + } + ] +} diff --git a/test cases/failing/90 kwarg dupe/test.json b/test cases/failing/90 kwarg dupe/test.json new file mode 100644 index 0000000..a8df75d --- /dev/null +++ b/test cases/failing/90 kwarg dupe/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/90 kwarg dupe/meson.build:5:0: ERROR: Entry \"install\" defined both as a keyword argument and in a \"kwarg\" entry." + } + ] +} diff --git a/test cases/failing/91 missing pch file/test.json b/test cases/failing/91 missing pch file/test.json new file mode 100644 index 0000000..166f627 --- /dev/null +++ b/test cases/failing/91 missing pch file/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "comment": "literal 'pch/prog.h' from meson.build appears in output, irrespective of os.path.sep", + "line": "test cases/failing/91 missing pch file/meson.build:2:0: ERROR: File pch/prog.h does not exist." + } + ] +} diff --git a/test cases/failing/92 pch source different folder/test.json b/test cases/failing/92 pch source different folder/test.json new file mode 100644 index 0000000..d94db50 --- /dev/null +++ b/test cases/failing/92 pch source different folder/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/92 pch source different folder/meson.build:4:0: ERROR: PCH files must be stored in the same folder." + } + ] +} diff --git a/test cases/failing/93 vala without c/test.json b/test cases/failing/93 vala without c/test.json new file mode 100644 index 0000000..6185b7e --- /dev/null +++ b/test cases/failing/93 vala without c/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/93 vala without c/meson.build:2:0: ERROR: Compiling Vala requires C. Add C to your project languages and rerun Meson." + } + ] +} diff --git a/test cases/failing/94 unknown config tool/test.json b/test cases/failing/94 unknown config tool/test.json new file mode 100644 index 0000000..a001152 --- /dev/null +++ b/test cases/failing/94 unknown config tool/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/94 unknown config tool/meson.build:2:0: ERROR: Dependency \"no-such-config-tool\" not found" + } + ] +} diff --git a/test cases/failing/95 custom target install data/test.json b/test cases/failing/95 custom target install data/test.json new file mode 100644 index 0000000..64ef530 --- /dev/null +++ b/test cases/failing/95 custom target install data/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/95 custom target install data/meson.build:11:0: ERROR: Argument must be string or file." + } + ] +} diff --git a/test cases/failing/96 add dict non string key/test.json b/test cases/failing/96 add dict non string key/test.json new file mode 100644 index 0000000..5fd4033 --- /dev/null +++ b/test cases/failing/96 add dict non string key/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/96 add dict non string key/meson.build:9:0: ERROR: Key must be a string" + } + ] +} diff --git a/test cases/failing/97 add dict duplicate keys/test.json b/test cases/failing/97 add dict duplicate keys/test.json new file mode 100644 index 0000000..9d01551 --- /dev/null +++ b/test cases/failing/97 add dict duplicate keys/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/97 add dict duplicate keys/meson.build:9:0: ERROR: Duplicate dictionary key: myKey" + } + ] +} diff --git a/test cases/failing/98 fallback consistency/test.json b/test cases/failing/98 fallback consistency/test.json new file mode 100644 index 0000000..fd77bad --- /dev/null +++ b/test cases/failing/98 fallback consistency/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/98 fallback consistency/meson.build:4:0: ERROR: Inconsistency: Subproject has overridden the dependency with another variable than 'dep2'" + } + ] +} diff --git a/test cases/failing/99 no native prop/test.json b/test cases/failing/99 no native prop/test.json new file mode 100644 index 0000000..8c320d9 --- /dev/null +++ b/test cases/failing/99 no native prop/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/99 no native prop/meson.build:3:0: ERROR: Unknown native property: nonexisting." + } + ] +} diff --git a/test cases/frameworks/1 boost/meson.build b/test cases/frameworks/1 boost/meson.build index 501ed29..6c23360 100644 --- a/test cases/frameworks/1 boost/meson.build +++ b/test cases/frameworks/1 boost/meson.build @@ -13,7 +13,7 @@ endif # within one project. The need to be independent of each other. # Use one without a library dependency and one with it. -linkdep = dependency('boost', static: s, modules : ['thread', 'system']) +linkdep = dependency('boost', static: s, modules : ['thread', 'system', 'date_time']) testdep = dependency('boost', static: s, modules : ['unit_test_framework']) nomoddep = dependency('boost', static: s) extralibdep = dependency('boost', static: s, modules : ['thread', 'system', 'date_time', 'log_setup', 'log', 'filesystem', 'regex']) diff --git a/test cases/frameworks/2 gtest/meson.build b/test cases/frameworks/2 gtest/meson.build index 2d93b52..ea3ef48 100644 --- a/test cases/frameworks/2 gtest/meson.build +++ b/test cases/frameworks/2 gtest/meson.build @@ -8,7 +8,7 @@ endif gtest_nomain = dependency('gtest', main : false, method : 'system') e = executable('testprog', 'test.cc', dependencies : gtest) -test('gtest test', e) +test('gtest test', e, protocol : 'gtest') e = executable('testprog_nomain', 'test_nomain.cc', dependencies : gtest_nomain) -test('gtest nomain test', e) +test('gtest nomain test', e, protocol : 'gtest') diff --git a/test cases/frameworks/21 libwmf/meson.build b/test cases/frameworks/21 libwmf/meson.build index 6952bf7..9dbab6a 100644 --- a/test cases/frameworks/21 libwmf/meson.build +++ b/test cases/frameworks/21 libwmf/meson.build @@ -1,7 +1,7 @@ project('libwmf test', 'c') wm = find_program('libwmf-config', required : false) -if not wm.found() +if not wm.found() or meson.is_cross_build() error('MESON_SKIP_TEST: libwmf-config not installed') endif diff --git a/test cases/frameworks/23 hotdoc/test.json b/test cases/frameworks/23 hotdoc/test.json index e2d4992..8dd07e0 100644 --- a/test cases/frameworks/23 hotdoc/test.json +++ b/test cases/frameworks/23 hotdoc/test.json @@ -314,5 +314,8 @@ {"type": "file", "file": "usr/share/doc/foobar/html/assets/fonts/dumped.trie"}, {"type": "file", "file": "usr/share/doc/foobar/html/assets/images/home.svg"}, {"type": "file", "file": "usr/share/doc/foobar/html/assets/images/dumped.trie"} - ] + ], + "tools": { + "hotdoc": ">=0.1.0" + } } diff --git a/test cases/frameworks/7 gnome/gir/dep1/dep3/dep3.c b/test cases/frameworks/7 gnome/gir/dep1/dep3/dep3.c new file mode 100644 index 0000000..ee5c5e1 --- /dev/null +++ b/test cases/frameworks/7 gnome/gir/dep1/dep3/dep3.c @@ -0,0 +1,124 @@ +#include "dep3.h" + +struct _MesonDep3 +{ + GObject parent_instance; + + gchar *msg; +}; + +G_DEFINE_TYPE (MesonDep3, meson_dep3, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_MSG, + LAST_PROP +}; + +static GParamSpec *gParamSpecs [LAST_PROP]; + +/** + * meson_dep3_new: + * @msg: The message to set. + * + * Allocates a new #MesonDep3. + * + * Returns: (transfer full): a #MesonDep3. + */ +MesonDep3 * +meson_dep3_new (const gchar *msg) +{ + g_return_val_if_fail (msg != NULL, NULL); + + return g_object_new (MESON_TYPE_DEP3, + "message", msg, + NULL); +} + +static void +meson_dep3_finalize (GObject *object) +{ + MesonDep3 *self = (MesonDep3 *)object; + + g_clear_pointer (&self->msg, g_free); + + G_OBJECT_CLASS (meson_dep3_parent_class)->finalize (object); +} + +static void +meson_dep3_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MesonDep3 *self = MESON_DEP3 (object); + + switch (prop_id) + { + case PROP_MSG: + g_value_set_string (value, self->msg); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +meson_dep3_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MesonDep3 *self = MESON_DEP3 (object); + + switch (prop_id) + { + case PROP_MSG: + self->msg = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +meson_dep3_class_init (MesonDep3Class *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meson_dep3_finalize; + object_class->get_property = meson_dep3_get_property; + object_class->set_property = meson_dep3_set_property; + + gParamSpecs [PROP_MSG] = + g_param_spec_string ("message", + "Message", + "The message to print.", + NULL, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs); +} + +static void +meson_dep3_init (MesonDep3 *self) +{ +} + +/** + * meson_dep3_return_message: + * @self: a #MesonDep3. + * + * Returns the message. + * + * Returns: (transfer none): a const gchar* + */ +const gchar* +meson_dep3_return_message (MesonDep3 *self) +{ + g_return_val_if_fail (MESON_IS_DEP3 (self), NULL); + + return (const gchar*) self->msg; +} diff --git a/test cases/frameworks/7 gnome/gir/dep1/dep3/dep3.h b/test cases/frameworks/7 gnome/gir/dep1/dep3/dep3.h new file mode 100644 index 0000000..9883d76 --- /dev/null +++ b/test cases/frameworks/7 gnome/gir/dep1/dep3/dep3.h @@ -0,0 +1,21 @@ +#ifndef MESON_DEP3_H +#define MESON_DEP3_H + +#if !defined (MESON_TEST) +#error "MESON_TEST not defined." +#endif + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define MESON_TYPE_DEP3 (meson_dep3_get_type()) + +G_DECLARE_FINAL_TYPE (MesonDep3, meson_dep3, MESON, DEP3, GObject) + +MesonDep3 *meson_dep3_new (const gchar *msg); +const gchar *meson_dep3_return_message (MesonDep3 *self); + +G_END_DECLS + +#endif /* MESON_DEP3_H */ diff --git a/test cases/frameworks/7 gnome/gir/dep1/dep3/meson.build b/test cases/frameworks/7 gnome/gir/dep1/dep3/meson.build new file mode 100644 index 0000000..1ef7765 --- /dev/null +++ b/test cases/frameworks/7 gnome/gir/dep1/dep3/meson.build @@ -0,0 +1,22 @@ +dep3sources = ['dep3.c', 'dep3.h'] + +dep3lib = shared_library( + 'dep3lib', + sources : dep3sources, + dependencies : gobj, + install : true +) + +dep3gir = gnome.generate_gir( + dep3lib, + sources : dep3sources, + nsversion : '1.0', + namespace : 'MesonDep3', + symbol_prefix : 'meson', + identifier_prefix : 'Meson', + includes : ['GObject-2.0'], + install : true +) + +dep3_dep = declare_dependency(link_with : dep3lib, + sources : [dep3gir]) diff --git a/test cases/frameworks/7 gnome/gir/dep1/meson.build b/test cases/frameworks/7 gnome/gir/dep1/meson.build index baa0b1d..2f03ede 100644 --- a/test cases/frameworks/7 gnome/gir/dep1/meson.build +++ b/test cases/frameworks/7 gnome/gir/dep1/meson.build @@ -1,4 +1,5 @@ subdir('dep2') +subdir('dep3') dep1sources = ['dep1.c', 'dep1.h'] @@ -20,11 +21,11 @@ dep1gir = gnome.generate_gir( symbol_prefix : 'meson', identifier_prefix : 'Meson', header: 'dep1.h', - includes : ['GObject-2.0', 'MesonDep2-1.0'], + includes : ['GObject-2.0', 'MesonDep2-1.0', dep3gir[0]], dependencies : [dep2_dep], install : true ) dep1_dep = declare_dependency(link_with : dep1lib, - dependencies : [dep2_dep], + dependencies : [dep2_dep, dep3_dep], sources : [dep1gir]) diff --git a/test cases/frameworks/7 gnome/gir/meson.build b/test cases/frameworks/7 gnome/gir/meson.build index 36bd09c..b1e0fa1 100644 --- a/test cases/frameworks/7 gnome/gir/meson.build +++ b/test cases/frameworks/7 gnome/gir/meson.build @@ -45,7 +45,7 @@ gnome.generate_gir( ) test('gobject introspection/c', girexe) -gir_paths = ':'.join([girlib.outdir(), dep1lib.outdir(), dep2lib.outdir()]) +gir_paths = ':'.join([girlib.outdir(), dep1lib.outdir(), dep2lib.outdir(), dep3lib.outdir()]) envdata = environment() envdata.append('GI_TYPELIB_PATH', gir_paths, separator : ':') envdata.append('LD_LIBRARY_PATH', gir_paths) diff --git a/test cases/frameworks/7 gnome/test.json b/test cases/frameworks/7 gnome/test.json index e69c9f0..badf410 100644 --- a/test cases/frameworks/7 gnome/test.json +++ b/test cases/frameworks/7 gnome/test.json @@ -13,12 +13,16 @@ {"type": "file", "platform": "cygwin", "file": "usr/lib/libdep1lib.dll.a"}, {"type": "expr", "file": "usr/lib/?libdep2lib.so"}, {"type": "file", "platform": "cygwin", "file": "usr/lib/libdep2lib.dll.a"}, + {"type": "expr", "file": "usr/lib/?libdep3lib.so"}, + {"type": "file", "platform": "cygwin", "file": "usr/lib/libdep3lib.dll.a"}, {"type": "file", "file": "usr/lib/girepository-1.0/Meson-1.0.typelib"}, {"type": "file", "file": "usr/lib/girepository-1.0/MesonDep1-1.0.typelib"}, {"type": "file", "file": "usr/lib/girepository-1.0/MesonDep2-1.0.typelib"}, + {"type": "file", "file": "usr/lib/girepository-1.0/MesonDep3-1.0.typelib"}, {"type": "file", "file": "usr/share/gir-1.0/Meson-1.0.gir"}, {"type": "file", "file": "usr/share/gir-1.0/MesonDep1-1.0.gir"}, {"type": "file", "file": "usr/share/gir-1.0/MesonDep2-1.0.gir"}, + {"type": "file", "file": "usr/share/gir-1.0/MesonDep3-1.0.gir"}, {"type": "file", "file": "usr/share/glib-2.0/schemas/com.github.meson.gschema.xml"}, {"type": "file", "file": "usr/share/simple-resources.gresource"}, {"type": "file", "file": "usr/include/enums6.h"}, diff --git a/test cases/kconfig/1 basic/.config b/test cases/keyval/1 basic/.config index 071d185..071d185 100644 --- a/test cases/kconfig/1 basic/.config +++ b/test cases/keyval/1 basic/.config diff --git a/test cases/kconfig/1 basic/meson.build b/test cases/keyval/1 basic/meson.build index 5dc8d19..fc7ddb3 100644 --- a/test cases/kconfig/1 basic/meson.build +++ b/test cases/keyval/1 basic/meson.build @@ -1,6 +1,6 @@ -project('kconfig basic test') +project('keyval basic test') -k = import('unstable-kconfig') +k = import('unstable-keyval') conf = k.load('.config') if not conf.has_key('CONFIG_VAL1') diff --git a/test cases/kconfig/2 subdir/.config b/test cases/keyval/2 subdir/.config index 0599d46..0599d46 100644 --- a/test cases/kconfig/2 subdir/.config +++ b/test cases/keyval/2 subdir/.config diff --git a/test cases/kconfig/2 subdir/dir/meson.build b/test cases/keyval/2 subdir/dir/meson.build index 12f1502..dc1b478 100644 --- a/test cases/kconfig/2 subdir/dir/meson.build +++ b/test cases/keyval/2 subdir/dir/meson.build @@ -1,5 +1,5 @@ -k = import('unstable-kconfig') +k = import('unstable-keyval') conf = k.load(meson.source_root() / '.config') diff --git a/test cases/kconfig/3 load_config files/meson.build b/test cases/keyval/2 subdir/meson.build index 1245b18..0651acf 100644 --- a/test cases/kconfig/3 load_config files/meson.build +++ b/test cases/keyval/2 subdir/meson.build @@ -1,4 +1,4 @@ -project('kconfig subdir test') +project('keyval subdir test') # Test into sub directory subdir('dir') diff --git a/test cases/kconfig/3 load_config files/dir/config b/test cases/keyval/3 load_config files/dir/config index 0599d46..0599d46 100644 --- a/test cases/kconfig/3 load_config files/dir/config +++ b/test cases/keyval/3 load_config files/dir/config diff --git a/test cases/kconfig/3 load_config files/dir/meson.build b/test cases/keyval/3 load_config files/dir/meson.build index d7b8d44..43fba13 100644 --- a/test cases/kconfig/3 load_config files/dir/meson.build +++ b/test cases/keyval/3 load_config files/dir/meson.build @@ -1,5 +1,5 @@ -k = import('unstable-kconfig') +k = import('unstable-keyval') conf = k.load(files('config')) diff --git a/test cases/kconfig/2 subdir/meson.build b/test cases/keyval/3 load_config files/meson.build index 1245b18..0651acf 100644 --- a/test cases/kconfig/2 subdir/meson.build +++ b/test cases/keyval/3 load_config files/meson.build @@ -1,4 +1,4 @@ -project('kconfig subdir test') +project('keyval subdir test') # Test into sub directory subdir('dir') diff --git a/test cases/kconfig/4 load_config builddir/config b/test cases/keyval/4 load_config builddir/config index 0599d46..0599d46 100644 --- a/test cases/kconfig/4 load_config builddir/config +++ b/test cases/keyval/4 load_config builddir/config diff --git a/test cases/kconfig/4 load_config builddir/meson.build b/test cases/keyval/4 load_config builddir/meson.build index 1924d23..1bb0285 100644 --- a/test cases/kconfig/4 load_config builddir/meson.build +++ b/test cases/keyval/4 load_config builddir/meson.build @@ -1,6 +1,6 @@ -project('kconfig builddir test') +project('keyval builddir test') -k = import('unstable-kconfig') +k = import('unstable-keyval') out_conf = configure_file(input: 'config', output: 'out-config', copy: true) conf = k.load(out_conf) diff --git a/test cases/linuxlike/13 cmake dependency/cmVers.sh b/test cases/linuxlike/13 cmake dependency/cmVers.sh new file mode 100755 index 0000000..70809de --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/cmVers.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +VERS=$(cmake --version | grep "cmake version") +VERS=${VERS//cmake version/} + +echo -n $VERS diff --git a/test cases/linuxlike/13 cmake dependency/meson.build b/test cases/linuxlike/13 cmake dependency/meson.build index 79acc83..93824ab 100644 --- a/test cases/linuxlike/13 cmake dependency/meson.build +++ b/test cases/linuxlike/13 cmake dependency/meson.build @@ -6,6 +6,9 @@ if not find_program('cmake', required: false).found() error('MESON_SKIP_TEST cmake binary not available.') endif +# CMake version +cm_vers = run_command(find_program('./cmVers.sh')).stdout().strip() + # Zlib is probably on all dev machines. dep = dependency('ZLIB', version : '>=1.2', method : 'cmake') @@ -48,14 +51,16 @@ depm1 = dependency('SomethingLikeZLIB', required : true, components : 'required_ depm2 = dependency('SomethingLikeZLIB', required : true, components : 'required_comp', method : 'cmake', cmake_module_path : ['cmake']) depm3 = dependency('SomethingLikeZLIB', required : true, components : ['required_comp'], cmake_module_path : 'cmake') -# Test some edge cases with spaces, etc. +# Test some edge cases with spaces, etc. (but only for CMake >= 3.15) -testDep1 = dependency('ImportedTarget', required : true, method : 'cmake', cmake_module_path : 'cmake', modules: 'mesonTestLibDefs') -testDep2 = dependency('ImportedTarget', required : true, method : 'cmake', cmake_module_path : 'cmake', modules : ['MesonTest::TestLibDefs']) -testFlagSet1 = executable('testFlagSet1', ['testFlagSet.c'], dependencies: [testDep1]) -testFlagSet2 = executable('testFlagSet2', ['testFlagSet.c'], dependencies: [testDep2]) -test('testFlagSetTest1', testFlagSet1) -test('testFlagSetTest2', testFlagSet2) +if cm_vers.version_compare('>=3.15') + testDep1 = dependency('ImportedTarget', required : true, method : 'cmake', cmake_module_path : 'cmake', modules: 'mesonTestLibDefs') + testDep2 = dependency('ImportedTarget', required : true, method : 'cmake', cmake_module_path : 'cmake', modules : ['MesonTest::TestLibDefs']) + testFlagSet1 = executable('testFlagSet1', ['testFlagSet.c'], dependencies: [testDep1]) + testFlagSet2 = executable('testFlagSet2', ['testFlagSet.c'], dependencies: [testDep2]) + test('testFlagSetTest1', testFlagSet1) + test('testFlagSetTest2', testFlagSet2) +endif # Try to compile a test that takes a dep and an include_directories diff --git a/test cases/unit/35 dist script/meson.build b/test cases/unit/35 dist script/meson.build index fd672a9..2ae9438 100644 --- a/test cases/unit/35 dist script/meson.build +++ b/test cases/unit/35 dist script/meson.build @@ -5,3 +5,4 @@ exe = executable('comparer', 'prog.c') test('compare', exe) meson.add_dist_script('replacer.py', '"incorrect"', '"correct"') +meson.add_dist_script(find_program('replacer.py'), '"incorrect"', '"correct"') diff --git a/test cases/unit/36 exe_wrapper behaviour/meson.build b/test cases/unit/36 exe_wrapper behaviour/meson.build index 16a44d5..d0817ba 100644 --- a/test cases/unit/36 exe_wrapper behaviour/meson.build +++ b/test cases/unit/36 exe_wrapper behaviour/meson.build @@ -1,7 +1,7 @@ project('exe wrapper behaviour', 'c') assert(meson.is_cross_build(), 'not setup as cross build') -assert(meson.has_exe_wrapper(), 'exe wrapper not defined?') +assert(meson.has_exe_wrapper(), 'exe wrapper not defined?') # intentionally not changed to can_run_host_binaries, exe = executable('prog', 'prog.c') diff --git a/test cases/unit/40 external, internal library rpath/built library/meson.build b/test cases/unit/40 external, internal library rpath/built library/meson.build index f633996..07fe7bb 100644 --- a/test cases/unit/40 external, internal library rpath/built library/meson.build +++ b/test cases/unit/40 external, internal library rpath/built library/meson.build @@ -18,4 +18,9 @@ l = shared_library('bar_built', 'bar.c', if host_machine.system() == 'darwin' e = executable('prog', 'prog.c', link_with: l, install: true) test('testprog', e) +elif host_machine.system() == 'linux' + e = executable('prog', 'prog.c', link_with: l, install: true, + install_rpath: '$ORIGIN/..' / get_option('libdir'), + ) + test('testprog', e) endif diff --git a/test cases/unit/40 external, internal library rpath/external library/meson.build b/test cases/unit/40 external, internal library rpath/external library/meson.build index 3c311f5..06ffa0f 100644 --- a/test cases/unit/40 external, internal library rpath/external library/meson.build +++ b/test cases/unit/40 external, internal library rpath/external library/meson.build @@ -4,16 +4,16 @@ shared_library('foo_in_system', 'foo.c', install : true) l = shared_library('faa_pkg', 'faa.c', install: true) if host_machine.system() == 'darwin' - frameworks = ['-framework', 'CoreFoundation', '-framework', 'CoreMedia'] + ldflags = ['-framework', 'CoreFoundation', '-framework', 'CoreMedia'] allow_undef_args = ['-Wl,-undefined,dynamic_lookup'] else - frameworks = [] + ldflags = ['-Wl,-rpath,${libdir}'] allow_undef_args = [] endif pkg = import('pkgconfig') pkg.generate(name: 'faa_pkg', - libraries: [l] + frameworks, + libraries: [l] + ldflags, description: 'FAA, a pkg-config test library') # cygwin DLLs can't have undefined symbols diff --git a/test cases/unit/57 introspection/meson.build b/test cases/unit/57 introspection/meson.build index 9716eae..5d4dd02 100644 --- a/test cases/unit/57 introspection/meson.build +++ b/test cases/unit/57 introspection/meson.build @@ -13,7 +13,7 @@ test_bool = not test_bool set_variable('list_test_plusassign', []) list_test_plusassign += ['bugs everywhere'] -if false +if not true vers_str = '<=99.9.9' dependency('somethingthatdoesnotexist', required: true, version: '>=1.2.3') dependency('look_i_have_a_fallback', version: ['>=1.0.0', vers_str], fallback: ['oh_no', 'the_subproject_does_not_exist']) @@ -26,7 +26,7 @@ var1 = '1' var2 = 2.to_string() var3 = 'test3' -t1 = executable('test' + var1, ['t1.cpp'], link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2')) +t1 = executable('test' + var1, ['t1.cpp'], link_with: [sharedlib], install: not false, build_by_default: get_option('test_opt2')) 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]) @@ -46,3 +46,16 @@ message(osmesa_lib_name) # Infinite recursion gets triggered here when the param test('test case 1', t1) test('test case 2', t2) benchmark('benchmark 1', t3) + +### Stuff to test the AST JSON printer +foreach x : ['a', 'b', 'c'] + if x == 'a' + message('a') + elif x == 'b' + message('a') + else + continue + endif + break + continue +endforeach diff --git a/test cases/unit/72 cross test passed/exewrapper.py b/test cases/unit/72 cross test passed/exewrapper.py new file mode 100755 index 0000000..2c15ed6 --- /dev/null +++ b/test cases/unit/72 cross test passed/exewrapper.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Test that the MESON_EXE_WRAPPER environment variable is set + +import argparse +import os +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('binary') # unused, but needed for test behavior + parser.add_argument('--expected', action='store_true') + args = parser.parse_args() + + defined = 'MESON_EXE_WRAPPER' in os.environ + + if args.expected != defined: + print(os.environ, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test cases/unit/72 cross test passed/meson.build b/test cases/unit/72 cross test passed/meson.build new file mode 100644 index 0000000..4deb74b --- /dev/null +++ b/test cases/unit/72 cross test passed/meson.build @@ -0,0 +1,19 @@ +project( + 'cross test passed', + 'c', + version : '>= 0.51' +) + +e = executable('exec', 'src/main.c') + +py = import('python').find_installation() + +test('root', e) +test('main', py, args : [meson.current_source_dir() / 'script.py', e]) + +wrapper_args = [] +if get_option('expect') + wrapper_args += '--expected' +endif + +test('exe_wrapper in env', py, args : [meson.current_source_dir() / 'exewrapper.py', e, wrapper_args]) diff --git a/test cases/unit/72 cross test passed/meson_options.txt b/test cases/unit/72 cross test passed/meson_options.txt new file mode 100644 index 0000000..084c776 --- /dev/null +++ b/test cases/unit/72 cross test passed/meson_options.txt @@ -0,0 +1,5 @@ +option( + 'expect', + type : 'boolean', + value : false, +) diff --git a/test cases/unit/72 cross test passed/script.py b/test cases/unit/72 cross test passed/script.py new file mode 100644 index 0000000..257cd30 --- /dev/null +++ b/test cases/unit/72 cross test passed/script.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import subprocess +import sys + +if __name__ == "__main__": + sys.exit(subprocess.run(sys.argv[1:]).returncode) diff --git a/test cases/unit/72 cross test passed/src/main.c b/test cases/unit/72 cross test passed/src/main.c new file mode 100644 index 0000000..490b4a6 --- /dev/null +++ b/test cases/unit/72 cross test passed/src/main.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main(int argc, char const *argv[]) +{ + return 0; +} diff --git a/test cases/unit/72 summary/meson.build b/test cases/unit/73 summary/meson.build index df4540d..df4540d 100644 --- a/test cases/unit/72 summary/meson.build +++ b/test cases/unit/73 summary/meson.build diff --git a/test cases/unit/72 summary/subprojects/sub/meson.build b/test cases/unit/73 summary/subprojects/sub/meson.build index e7d7833..e7d7833 100644 --- a/test cases/unit/72 summary/subprojects/sub/meson.build +++ b/test cases/unit/73 summary/subprojects/sub/meson.build diff --git a/test cases/unit/72 summary/subprojects/sub2/meson.build b/test cases/unit/73 summary/subprojects/sub2/meson.build index 86b9cfd..86b9cfd 100644 --- a/test cases/unit/72 summary/subprojects/sub2/meson.build +++ b/test cases/unit/73 summary/subprojects/sub2/meson.build diff --git a/test cases/unit/73 wrap file url/meson.build b/test cases/unit/74 wrap file url/meson.build index 3bd3b25..3bd3b25 100644 --- a/test cases/unit/73 wrap file url/meson.build +++ b/test cases/unit/74 wrap file url/meson.build diff --git a/test cases/unit/73 wrap file url/subprojects/foo-patch.tar.xz b/test cases/unit/74 wrap file url/subprojects/foo-patch.tar.xz Binary files differindex fdb026c..fdb026c 100644 --- a/test cases/unit/73 wrap file url/subprojects/foo-patch.tar.xz +++ b/test cases/unit/74 wrap file url/subprojects/foo-patch.tar.xz diff --git a/test cases/unit/73 wrap file url/subprojects/foo.tar.xz b/test cases/unit/74 wrap file url/subprojects/foo.tar.xz Binary files differindex 2ed6ab4..2ed6ab4 100644 --- a/test cases/unit/73 wrap file url/subprojects/foo.tar.xz +++ b/test cases/unit/74 wrap file url/subprojects/foo.tar.xz diff --git a/test cases/unit/75 dep files/foo.c b/test cases/unit/75 dep files/foo.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/unit/75 dep files/foo.c diff --git a/test cases/unit/74 dep files/meson.build b/test cases/unit/75 dep files/meson.build index 4829f56..4829f56 100644 --- a/test cases/unit/74 dep files/meson.build +++ b/test cases/unit/75 dep files/meson.build diff --git a/test cases/unit/76 pkgconfig prefixes/client/client.c b/test cases/unit/76 pkgconfig prefixes/client/client.c new file mode 100644 index 0000000..be9bead --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/client/client.c @@ -0,0 +1,8 @@ +#include <val2.h> +#include <stdio.h> + +int main(int argc, char **argv) +{ + printf("%d\n", val2()); + return 0; +} diff --git a/test cases/unit/76 pkgconfig prefixes/client/meson.build b/test cases/unit/76 pkgconfig prefixes/client/meson.build new file mode 100644 index 0000000..491937b --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/client/meson.build @@ -0,0 +1,3 @@ +project('client', 'c') +val2_dep = dependency('val2') +executable('client', 'client.c', dependencies : [val2_dep], install: true) diff --git a/test cases/unit/76 pkgconfig prefixes/val1/meson.build b/test cases/unit/76 pkgconfig prefixes/val1/meson.build new file mode 100644 index 0000000..cc63e31 --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/val1/meson.build @@ -0,0 +1,5 @@ +project('val1', 'c') +val1 = shared_library('val1', 'val1.c', install: true) +install_headers('val1.h') +pkgconfig = import('pkgconfig') +pkgconfig.generate(val1, libraries : ['-Wl,-rpath,${libdir}']) diff --git a/test cases/unit/76 pkgconfig prefixes/val1/val1.c b/test cases/unit/76 pkgconfig prefixes/val1/val1.c new file mode 100644 index 0000000..591e521 --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/val1/val1.c @@ -0,0 +1,3 @@ +#include "val1.h" + +int val1(void) { return 1; } diff --git a/test cases/unit/76 pkgconfig prefixes/val1/val1.h b/test cases/unit/76 pkgconfig prefixes/val1/val1.h new file mode 100644 index 0000000..6bd435e --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/val1/val1.h @@ -0,0 +1 @@ +int val1(void); diff --git a/test cases/unit/76 pkgconfig prefixes/val2/meson.build b/test cases/unit/76 pkgconfig prefixes/val2/meson.build new file mode 100644 index 0000000..ce69481 --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/val2/meson.build @@ -0,0 +1,8 @@ +project('val2', 'c') +val1_dep = dependency('val1') +val2 = shared_library('val2', 'val2.c', + dependencies : [val1_dep], + install: true) +install_headers('val2.h') +pkgconfig = import('pkgconfig') +pkgconfig.generate(val2, libraries : ['-Wl,-rpath,${libdir}']) diff --git a/test cases/unit/76 pkgconfig prefixes/val2/val2.c b/test cases/unit/76 pkgconfig prefixes/val2/val2.c new file mode 100644 index 0000000..d7d4857 --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/val2/val2.c @@ -0,0 +1,4 @@ +#include "val1.h" +#include "val2.h" + +int val2(void) { return val1() + 2; } diff --git a/test cases/unit/76 pkgconfig prefixes/val2/val2.h b/test cases/unit/76 pkgconfig prefixes/val2/val2.h new file mode 100644 index 0000000..995023d --- /dev/null +++ b/test cases/unit/76 pkgconfig prefixes/val2/val2.h @@ -0,0 +1 @@ +int val2(void); diff --git a/test cases/unit/76 subdir libdir/meson.build b/test cases/unit/76 subdir libdir/meson.build new file mode 100644 index 0000000..5099c91 --- /dev/null +++ b/test cases/unit/76 subdir libdir/meson.build @@ -0,0 +1,2 @@ +project('toplevel', 'c') +subproject('flub') diff --git a/test cases/unit/76 subdir libdir/subprojects/flub/meson.build b/test cases/unit/76 subdir libdir/subprojects/flub/meson.build new file mode 100644 index 0000000..7bfd2c5 --- /dev/null +++ b/test cases/unit/76 subdir libdir/subprojects/flub/meson.build @@ -0,0 +1 @@ +project('subflub', 'c') diff --git a/test cases/unit/77 global-rpath/meson.build b/test cases/unit/77 global-rpath/meson.build new file mode 100644 index 0000000..c67d9e0 --- /dev/null +++ b/test cases/unit/77 global-rpath/meson.build @@ -0,0 +1,3 @@ +project('global-rpath', 'cpp') +yonder_dep = dependency('yonder') +executable('rpathified', 'rpathified.cpp', dependencies: [yonder_dep], install: true) diff --git a/test cases/unit/77 global-rpath/rpathified.cpp b/test cases/unit/77 global-rpath/rpathified.cpp new file mode 100644 index 0000000..3788906 --- /dev/null +++ b/test cases/unit/77 global-rpath/rpathified.cpp @@ -0,0 +1,6 @@ +#include <yonder.h> +#include <string.h> +int main(int argc, char **argv) +{ + return strcmp(yonder(), "AB54 6BR"); +} diff --git a/test cases/unit/77 global-rpath/yonder/meson.build b/test cases/unit/77 global-rpath/yonder/meson.build new file mode 100644 index 0000000..e32f383 --- /dev/null +++ b/test cases/unit/77 global-rpath/yonder/meson.build @@ -0,0 +1,5 @@ +project('yonder', 'cpp') +yonder = shared_library('yonder', 'yonder.cpp', install: true) +install_headers('yonder.h') +pkgconfig = import('pkgconfig') +pkgconfig.generate(yonder) diff --git a/test cases/unit/77 global-rpath/yonder/yonder.cpp b/test cases/unit/77 global-rpath/yonder/yonder.cpp new file mode 100644 index 0000000..b182d34 --- /dev/null +++ b/test cases/unit/77 global-rpath/yonder/yonder.cpp @@ -0,0 +1,3 @@ +#include "yonder.h" + +char *yonder(void) { return "AB54 6BR"; } diff --git a/test cases/unit/77 global-rpath/yonder/yonder.h b/test cases/unit/77 global-rpath/yonder/yonder.h new file mode 100644 index 0000000..9d9ad16 --- /dev/null +++ b/test cases/unit/77 global-rpath/yonder/yonder.h @@ -0,0 +1 @@ +char *yonder(void); diff --git a/test cases/warning/1 version for string div/test.json b/test cases/warning/1 version for string div/test.json new file mode 100644 index 0000000..c37931a --- /dev/null +++ b/test cases/warning/1 version for string div/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "comment": "literal '/' appears in output, irrespective of os.path.sep, as that's the operator", + "line": "WARNING: Project targeting '>=0.48.0' but tried to use feature introduced in '0.49.0': / with string arguments." + } + ] +} diff --git a/test cases/warning/2 languages missing native/test.json b/test cases/warning/2 languages missing native/test.json new file mode 100644 index 0000000..36da0a7 --- /dev/null +++ b/test cases/warning/2 languages missing native/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/warning/2 languages missing native/meson.build:2: WARNING: add_languages is missing native:, assuming languages are wanted for both host and build." + } + ] +} diff --git a/tools/boost_names.py b/tools/boost_names.py index d26d34b..b66c6cc 100755 --- a/tools/boost_names.py +++ b/tools/boost_names.py @@ -43,10 +43,10 @@ export_modules = False class BoostLibrary(): def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]): self.name = name - self.shared = shared - self.static = static - self.single = single - self.multi = multi + self.shared = sorted(set(shared)) + self.static = sorted(set(static)) + self.single = sorted(set(single)) + self.multi = sorted(set(multi)) def __lt__(self, other: T.Any) -> T.Union[bool, 'NotImplemented']: if isinstance(other, BoostLibrary): @@ -99,15 +99,35 @@ def get_libraries(jamfile: Path) -> T.List[BoostLibrary]: cmds = raw.split(';') # Commands always terminate with a ; (I hope) cmds = [x.strip() for x in cmds] # Some cleanup + project_usage_requirements: T.List[str] = [] + # "Parse" the relevant sections for i in cmds: parts = i.split(' ') - parts = [x for x in parts if x not in ['', ':']] + parts = [x for x in parts if x not in ['']] if not parts: continue - # Parese libraries - if parts[0] in ['lib', 'boost-lib']: + # Parse project + if parts[0] in ['project']: + attributes: T.Dict[str, T.List[str]] = {} + curr: T.Optional[str] = None + + for j in parts: + if j == ':': + curr = None + elif curr is None: + curr = j + else: + if curr not in attributes: + attributes[curr] = [] + attributes[curr] += [j] + + if 'usage-requirements' in attributes: + project_usage_requirements = attributes['usage-requirements'] + + # Parse libraries + elif parts[0] in ['lib', 'boost-lib']: assert len(parts) >= 2 # Get and check the library name @@ -117,28 +137,36 @@ def get_libraries(jamfile: Path) -> T.List[BoostLibrary]: if not lname.startswith('boost_'): continue + # Count `:` to only select the 'usage-requirements' + # See https://boostorg.github.io/build/manual/master/index.html#bbv2.main-target-rule-syntax + colon_counter = 0 + usage_requirements: T.List[str] = [] + for j in parts: + if j == ':': + colon_counter += 1 + elif colon_counter >= 4: + usage_requirements += [j] + # Get shared / static defines shared: T.List[str] = [] static: T.List[str] = [] single: T.List[str] = [] multi: T.List[str] = [] - for j in parts: + for j in usage_requirements + project_usage_requirements: m1 = re.match(r'<link>shared:<define>(.*)', j) m2 = re.match(r'<link>static:<define>(.*)', j) m3 = re.match(r'<threading>single:<define>(.*)', j) m4 = re.match(r'<threading>multi:<define>(.*)', j) if m1: - shared += [m1.group(1)] + shared += [f'-D{m1.group(1)}'] if m2: - static += [m2.group(1)] + static += [f'-D{m2.group(1)}'] if m3: - single += [m3.group(1)] + single +=[f'-D{m3.group(1)}'] if m4: - multi += [m4.group(1)] + multi += [f'-D{m4.group(1)}'] - shared = [f'-D{x}' for x in shared] - static = [f'-D{x}' for x in static] libs += [BoostLibrary(lname, shared, static, single, multi)] return libs diff --git a/tools/dircondenser.py b/tools/dircondenser.py index 023c14e..0e28bec 100755 --- a/tools/dircondenser.py +++ b/tools/dircondenser.py @@ -74,6 +74,10 @@ def condense(dirname: str): #print('git mv "%s" "%s"' % (old_name, new_name)) subprocess.check_call(['git', 'mv', old_name, new_name]) replacements.append((old_name, new_name)) + # update any appearances of old_name in expected stdout in test.json + json = os.path.join(new_name, 'test.json') + if os.path.isfile(json): + replace_source(json, [(old_name, new_name)]) os.chdir(curdir) replace_source('run_unittests.py', replacements) replace_source('run_project_tests.py', replacements) |