diff options
56 files changed, 1175 insertions, 618 deletions
diff --git a/.github/workflows/ci_frameworks.yml b/.github/workflows/ci_frameworks.yml deleted file mode 100644 index 1ace2a6..0000000 --- a/.github/workflows/ci_frameworks.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: ci_frameworks - -on: - push: - paths: - - "mesonbuild/dependencies/**" - - "test cases/frameworks/**" - - ".github/workflows/ci_frameworks.yml" - pull_request: - paths: - - "mesonbuild/dependencies/**" - - "test cases/frameworks/**" - - ".github/workflows/ci_frameworks.yml" - -jobs: - - scalapackMacOS: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - run: python -m pip install -e . - - run: brew install pkg-config ninja gcc openmpi lapack scalapack - - run: meson setup "test cases/frameworks/30 scalapack" build - - run: meson compile -C build - - uses: actions/upload-artifact@v1 - if: failure() - with: - name: Scalapack_Mac_build - path: build/meson-logs/meson-log.txt - - run: meson test -C build -v - - uses: actions/upload-artifact@v1 - if: failure() - with: - name: Scalapack_Mac_test - path: build/meson-logs/testlog.txt - - HDF5macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - run: python -m pip install -e . - - run: brew install pkg-config ninja gcc hdf5 - - run: meson setup "test cases/frameworks/25 hdf5" build -Dmethod=config-tool - - run: meson compile -C build - - uses: actions/upload-artifact@v1 - if: failure() - with: - name: HDF5_Mac_build - path: build/meson-logs/meson-log.txt - - run: meson test -C build -v - - uses: actions/upload-artifact@v1 - if: failure() - with: - name: HDF5_Mac_test - path: build/meson-logs/testlog.txt - - Qt4macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - run: python -m pip install -e . - - run: brew install pkg-config ninja gcc - - run: brew tap cartr/qt4 - - run: brew install qt@4 - - run: meson setup "test cases/frameworks/4 qt" build -Drequired=qt4 - - run: meson compile -C build - - uses: actions/upload-artifact@v1 - if: failure() - with: - name: Qt4_Mac_build - path: build/meson-logs/meson-log.txt - - run: meson test -C build -v - - uses: actions/upload-artifact@v1 - if: failure() - with: - name: Qt4_Mac_test - path: build/meson-logs/testlog.txt diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..bf559e8 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,85 @@ +name: macos + +on: + push: + paths: + - "mesonbuild/**" + - "test cases/**" + - ".github/workflows/macos.yml" + pull_request: + paths: + - "mesonbuild/**" + - "test cases/**" + - ".github/workflows/macos.yml" + +jobs: + unittests-appleclang: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - run: | + python -m pip install --upgrade pip + python -m pip install pytest pytest-xdist jsonschema + - run: brew install pkg-config ninja llvm qt + - env: + CPPFLAGS: "-I/usr/local/include" + LDFLAGS: "-L/usr/local/lib" + MESON_UNIT_TEST_BACKEND: ninja + # These cannot evaluate anything, so we cannot set PATH or SDKROOT here + run: | + export SDKROOT="$(xcodebuild -version -sdk macosx Path)" + export PATH="$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin" + ./run_unittests.py + + project-tests-appleclang: + runs-on: macos-latest + strategy: + matrix: + unity: ["on", "off"] + steps: + - uses: actions/checkout@v2 + # use python3 from homebrew because it is a valid framework, unlike the actions one: + # https://github.com/actions/setup-python/issues/58 + - run: brew install pkg-config ninja llvm qt boost ldc hdf5 openmpi lapack scalapack sdl2 python3 + - run: | + python3 -m pip install --upgrade setuptools + python3 -m pip install --upgrade pip + python3 -m pip install cython + - env: + CPPFLAGS: "-I/usr/local/include" + LDFLAGS: "-L/usr/local/lib" + MESON_ARGS: --unity=${{ matrix.unity }} + CI: 1 + # These cannot evaluate anything, so we cannot set PATH or SDKROOT here + run: | + export SDKROOT="$(xcodebuild -version -sdk macosx Path)" + export PATH="$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin" + ./run_project_tests.py --backend=ninja + + Qt4macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + - run: python -m pip install -e . + - run: brew install pkg-config ninja gcc + - run: brew tap cartr/qt4 + - run: brew install qt@4 + - run: meson setup "test cases/frameworks/4 qt" build -Drequired=qt4 + - run: meson compile -C build + - uses: actions/upload-artifact@v1 + if: failure() + with: + name: Qt4_Mac_build + path: build/meson-logs/meson-log.txt + - run: meson test -C build -v + - uses: actions/upload-artifact@v1 + if: failure() + with: + name: Qt4_Mac_test + path: build/meson-logs/testlog.txt diff --git a/.github/workflows/msys2.yml b/.github/workflows/msys2.yml index 57a3f52..642e1de 100644 --- a/.github/workflows/msys2.yml +++ b/.github/workflows/msys2.yml @@ -2,7 +2,15 @@ name: msys2 on: push: + paths: + - "mesonbuild/**" + - "test cases/**" + - ".github/workflows/msys.yml" pull_request: + paths: + - "mesonbuild/**" + - "test cases/**" + - ".github/workflows/msys.yml" jobs: test: @@ -83,4 +91,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: ${{ matrix.NAME }} - path: meson-test-run.*
\ No newline at end of file + path: meson-test-run.* diff --git a/.travis.yml b/.travis.yml index ab317be..9f61c96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ branches: os: - linux - - osx compiler: - gcc @@ -25,10 +24,6 @@ services: - docker matrix: - exclude: - # On OS X gcc is just a wrapper around clang, so don't waste time testing that - - os: osx - compiler: gcc include: # Test cross builds separately, they do not use the global compiler # Also hijack one cross build to test long commandline handling codepath (and avoid overloading Travis) diff --git a/ci/travis_install.sh b/ci/travis_install.sh index d9d308a..bbeb100 100755 --- a/ci/travis_install.sh +++ b/ci/travis_install.sh @@ -4,19 +4,6 @@ set -e 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 && rm -f $(which pkg-config) - fi - python3 -m pip install jsonschema -elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - msg "Running Linux setup" - docker pull mesonbuild/eoan -fi - +msg "Running Linux setup" +docker pull mesonbuild/eoan msg "Setup finished" diff --git a/ci/travis_script.sh b/ci/travis_script.sh index 7e26b52..e60e3a3 100755 --- a/ci/travis_script.sh +++ b/ci/travis_script.sh @@ -4,16 +4,15 @@ set -e msg() { echo -e "\x1b[1;32mINFO: \x1b[37m$*\x1b[0m"; } -if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - # TODO enable coverage - #curl -s https://codecov.io/bash > upload.sh - #chmod +x upload.sh +# TODO enable coverage +#curl -s https://codecov.io/bash > upload.sh +#chmod +x upload.sh - # We need to copy the current checkout inside the Docker container, - # because it has the MR id to be tested checked out. +# We need to copy the current checkout inside the Docker container, +# because it has the MR id to be tested checked out. - msg "Generating runner:" - cat <<EOF | tee run.sh +msg "Generating runner:" +cat <<EOF | tee run.sh #!/bin/bash set -e @@ -50,23 +49,9 @@ ADD . /root EOF - msg "Building the docker image..." - docker build -t test_img . - - msg "Start running tests" - #ci_env=`bash <(curl -s https://codecov.io/env)` - docker run --security-opt seccomp:unconfined test_img /root/run.sh - -elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - # Ensure that llvm is added after $PATH, otherwise the clang from that llvm install will be used instead of the native apple clang. - export SDKROOT=$(xcodebuild -version -sdk macosx Path) - export CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib - export OBJC=$CC - export OBJCXX=$CXX - export PATH=$HOME/tools:/usr/local/opt/qt/bin:$PATH:$(brew --prefix llvm)/bin - if test "$MESON_RSP_THRESHOLD" != "" - then - export MESON_RSP_THRESHOLD=$MESON_RSP_THRESHOLD - fi - ./run_tests.py $RUN_TESTS_ARGS --backend=ninja -- $MESON_ARGS -fi +msg "Building the docker image..." +docker build -t test_img . + +msg "Start running tests" +#ci_env=`bash <(curl -s https://codecov.io/env)` +docker run --security-opt seccomp:unconfined test_img /root/run.sh diff --git a/docs/markdown/Adding-new-projects-to-wrapdb.md b/docs/markdown/Adding-new-projects-to-wrapdb.md index a266379..5bdadfa 100644 --- a/docs/markdown/Adding-new-projects-to-wrapdb.md +++ b/docs/markdown/Adding-new-projects-to-wrapdb.md @@ -3,29 +3,34 @@ ## How it works -Each wrap repository has a master branch with only one initial commit and *no* wrap files. -And that is the only commit ever made on that branch. - -For every release of a project a new branch is created. The new branch is named after the -the upstream release number (e.g. `1.0.0`). This branch holds a wrap file for -this particular release. - -There are two types of wraps on WrapDB - regular wraps and wraps with Meson build -definition patches. A wrap file in a repository on WrapDB must have a name `upstream.wrap`. - -Wraps with Meson build definition patches work in much the same way as Debian: -we take the unaltered upstream source package and add a new build system to it as a patch. -These build systems are stored as Git repositories on GitHub. They only contain build definition files. -You may also think of them as an overlay to upstream source. - -Whenever a new commit is pushed into GitHub's project branch, a new wrap is generated -with an incremented version number. All the old releases remain unaltered. -New commits are always done via GitHub merge requests and must be reviewed by -someone other than the submitter. - -Note that your Git repo with wrap must not contain the subdirectory of the source -release. That gets added automatically by the service. You also must not commit -any source code from the original tarball into the wrap repository. +Each wrap repository has a master branch with only one initial commit +and *no* wrap files. And that is the only commit ever made on that +branch. + +For every release of a project a new branch is created. The new branch +is named after the the upstream release number (e.g. `1.0.0`). This +branch holds a wrap file for this particular release. + +There are two types of wraps on WrapDB - regular wraps and wraps with +Meson build definition patches. A wrap file in a repository on WrapDB +must have a name `upstream.wrap`. + +Wraps with Meson build definition patches work in much the same way as +Debian: we take the unaltered upstream source package and add a new +build system to it as a patch. These build systems are stored as Git +repositories on GitHub. They only contain build definition files. You +may also think of them as an overlay to upstream source. + +Whenever a new commit is pushed into GitHub's project branch, a new +wrap is generated with an incremented version number. All the old +releases remain unaltered. New commits are always done via GitHub +merge requests and must be reviewed by someone other than the +submitter. + +Note that your Git repo with wrap must not contain the subdirectory of +the source release. That gets added automatically by the service. You +also must not commit any source code from the original tarball into +the wrap repository. ## Choosing the repository name @@ -34,29 +39,32 @@ they should have the same name as the upstream projects. NOTE: Repo names must fully match this regexp: `[a-z0-9._]+`. -If the project provides a pkg-config file, then the repository name should be -the same as the pkg-config name. Usually this is the name of the -project, such as `libpng`. Sometimes it is slightly different, +If the project provides a pkg-config file, then the repository name +should be the same as the pkg-config name. Usually this is the name of +the project, such as `libpng`. Sometimes it is slightly different, however. As an example the libogg project's chosen pkg-config name is `ogg` instead of `libogg`, which is the reason why the repository is named plain `ogg`. -If there is no a pkg-config file, the name the project uses/promotes should be used, -lowercase only (Catch2 -> catch2). +If there is no a pkg-config file, the name the project uses/promotes +should be used, lowercase only (Catch2 -> catch2). If the project name is too generic or ambiguous (e.g. `benchmark`), -consider using `organization-project` naming format (e.g. `google-benchmark`). +consider using `organization-project` naming format (e.g. +`google-benchmark`). ## How to contribute a new wrap -If the project already uses Meson build system, then only a wrap file - `upstream.wrap` -should be provided. In other case a Meson build definition patch - a set of `meson.build` -files - should be also provided. +If the project already uses Meson build system, then only a wrap file +- `upstream.wrap` should be provided. In other case a Meson build +definition patch - a set of `meson.build` files - should be also +provided. ### Request a new repository -Create an issue on the [wrapdb bug tracker](https://github.com/mesonbuild/wrapdb/issues) -using *Title* and *Description* below as a template. +Create an issue on the [wrapdb bug +tracker](https://github.com/mesonbuild/wrapdb/issues) using *Title* +and *Description* below as a template. *Title:* `new wrap: <project_name>` @@ -66,17 +74,18 @@ upstream url: <link_to_updastream> version: <version_you_have_a_wrap_for> ``` -Wait until the new repository or branch is created. A link to the new repository or branch -will be posted in a comment to this issue. +Wait until the new repository or branch is created. A link to the new +repository or branch will be posted in a comment to this issue. -NOTE: Requesting a branch is not necessary. WrapDB maintainer can create the branch and -modify the PR accordingly if the project repository exists. +NOTE: Requesting a branch is not necessary. WrapDB maintainer can +create the branch and modify the PR accordingly if the project +repository exists. ### Creating the wrap contents -Setting up the contents might seem a bit counterintuitive at -first. Just remember that the outcome needs to have one (and only one) -commit that has all the build definition files (i.e. `meson.build` and +Setting up the contents might seem a bit counterintuitive at first. +Just remember that the outcome needs to have one (and only one) commit +that has all the build definition files (i.e. `meson.build` and `meson_options.txt` files) and _nothing else_. It is good practice to have this commit in a branch whose name matches the release as described above. @@ -132,6 +141,25 @@ Under macOS the command is the following: shasum -a 256 path/to/libfoo-1.0.0.tar.gz ``` +Next you need to add the entries that define what dependencies the +current project provides. This is important, as it is what makes +Meson's automatic dependency resolver work. It is done by adding a +`provide` entry at the end of the `upstream.wrap` file. Using the Ogg +library as an example, this is what it would look like: + +```ini +[provide] +ogg = ogg_dep +``` + +The `ogg` part on the left refers to the dependency name, which should +be the same as its Pkg-Config name. `ogg_dep` on the right refers to +the variable in the build definitions that provides the dependency. +Most commonly it holds the result of a `declare_dependency` call. If a +variable of that name is not defined, Meson will exit with a hard +error. For further details see [the main Wrap +manual](Wrap-dependency-system-manual.md). + Now you can create the build files and work on them until the project builds correctly. diff --git a/docs/markdown/Getting-meson.md b/docs/markdown/Getting-meson.md index e0e5722..3568dfc 100644 --- a/docs/markdown/Getting-meson.md +++ b/docs/markdown/Getting-meson.md @@ -23,12 +23,17 @@ a pull-request process that runs CI and tests several platforms. ## Installing Meson with pip Meson is available in the [Python Package Index] and can be installed with -`pip3 install meson` which requires root and will install it system-wide. +`sudo pip3 install meson` which requires root and will install it system-wide. + +If you have downloaded a copy of the meson sources already, you can install it +with `sudo pip3 install path/to/source/root/`. Alternatively, you can use `pip3 install --user meson` which will install it for your user and does not require any special privileges. This will install the package in `~/.local/`, so you will have to add `~/.local/bin` to your -`PATH`. +`PATH`, and `sudo meson install` will be completely broken since the +program will not be available to root. Only use a user copy of meson if you +do not care about installing projects as root. ## Installing Meson and Ninja with the MSI installer diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 3af78ef..aeaeccb 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -2409,6 +2409,12 @@ page](Configuration.md) It has three methods: - `has(varname)`: returns `true` if the specified variable is set +- `keys()`*(since 0.57.0)*: returns an array of keys of + the configuration data object. + + You can iterate over this array with the [`foreach` + statement](Syntax.md#foreach-statements). + - `merge_from(other)` *(since 0.42.0)*: takes as argument a different configuration data object and copies all entries from that object to the current. diff --git a/docs/markdown/Tutorial.md b/docs/markdown/Tutorial.md index bf337ba..85e345a 100644 --- a/docs/markdown/Tutorial.md +++ b/docs/markdown/Tutorial.md @@ -29,8 +29,11 @@ example. First we create a file `main.c` which holds the source. It looks like this. ```c -#include<stdio.h> +#include <stdio.h> +// +// main is where all program execution starts +// int main(int argc, char **argv) { printf("Hello there.\n"); return 0; @@ -53,7 +56,7 @@ to initialize the build by going into the source directory and issuing the following commands. ```console -$ meson builddir +$ meson setup builddir ``` We create a separate build directory to hold all of the compiler @@ -110,17 +113,40 @@ create a graphical window instead. We'll use the use GTK+. The new version looks like this. ```c -#include<gtk/gtk.h> -int main(int argc, char **argv) { - GtkWidget *win; - gtk_init(&argc, &argv); - win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(win), "Hello there"); - g_signal_connect(win, "destroy", G_CALLBACK(gtk_main_quit), NULL); - gtk_widget_show(win); - gtk_main(); -} +#include <gtk/gtk.h> + +// +// Should provided the active view for a GTK application +// +static void activate(GtkApplication* app, gpointer user_data) +{ + GtkWidget *window; + GtkWidget *label; + + window = gtk_application_window_new (app); + label = gtk_label_new("Hello GNOME!"); + gtk_container_add (GTK_CONTAINER (window), label); + gtk_window_set_title(GTK_WINDOW (window), "Welcome to GNOME"); + gtk_window_set_default_size(GTK_WINDOW (window), 200, 100); + gtk_widget_show_all(window); +} // end of function activate + +// +// main is where all program execution starts +// +int main(int argc, char **argv) +{ + GtkApplication *app; + int status; + + app = gtk_application_new(NULL, G_APPLICATION_FLAGS_NONE); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; +} // end of function main ``` Then we edit the Meson file, instructing it to find and use the GTK+ diff --git a/docs/markdown/snippets/keys_of_configuration_data.md b/docs/markdown/snippets/keys_of_configuration_data.md new file mode 100644 index 0000000..c0cbfc2 --- /dev/null +++ b/docs/markdown/snippets/keys_of_configuration_data.md @@ -0,0 +1,4 @@ +## Get keys of configuration data object + +All keys of the `configuration_data` object can be obtained with the `keys()` +method as an alphabetically sorted array. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 09f06da..15218c1 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -266,7 +266,7 @@ class NinjaRule: # expand variables in command command = ' '.join([self._quoter(x) for x in self.command + self.args]) estimate = len(command) - for m in re.finditer(r'(\${\w*}|\$\w*)?[^$]*', command): + for m in re.finditer(r'(\${\w+}|\$\w+)?[^$]*', command): if m.start(1) != -1: estimate -= m.end(1) - m.start(1) + 1 chunk = m.group(1) @@ -359,9 +359,9 @@ class NinjaBuildElement: rulename = self.rulename line = 'build {}{}: {} {}'.format(outs, implicit_outs, rulename, ins) if len(self.deps) > 0: - line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps]) + line += ' | ' + ' '.join([ninja_quote(x, True) for x in sorted(self.deps)]) if len(self.orderdeps) > 0: - line += ' || ' + ' '.join([ninja_quote(x, True) for x in self.orderdeps]) + line += ' || ' + ' '.join([ninja_quote(x, True) for x in sorted(self.orderdeps)]) line += '\n' # This is the only way I could find to make this work on all # platforms including Windows command shell. Slash is a dir separator @@ -1489,11 +1489,15 @@ int dummy; self.create_target_source_introspection(target, valac, args, all_files, []) return other_src[0], other_src[1], vala_c_src - def generate_rust_target(self, target): + def generate_rust_target(self, target: build.BuildTarget) -> None: rustc = target.compilers['rust'] # Rust compiler takes only the main file as input and # figures out what other files are needed via import # statements and magic. + base_proxy = self.get_base_options_for_target(target) + args = rustc.compiler_args() + # Compiler args for compiling this target + args += compilers.get_base_compile_args(base_proxy, rustc) main_rust_file = None for i in target.get_sources(): if not rustc.can_compile(i): @@ -1503,7 +1507,6 @@ int dummy; if main_rust_file is None: raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') target_name = os.path.join(target.subdir, target.get_filename()) - args = ['--crate-type'] if isinstance(target, build.Executable): cratetype = 'bin' elif hasattr(target, 'rust_crate_type'): @@ -1514,7 +1517,7 @@ int dummy; cratetype = 'rlib' else: raise InvalidArguments('Unknown target type for rustc.') - args.append(cratetype) + args.extend(['--crate-type', cratetype]) # If we're dynamically linking, add those arguments # @@ -1536,7 +1539,8 @@ int dummy; depfile = os.path.join(target.subdir, target.name + '.d') args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link'] args += target.get_extra_args('rust') - args += ['-o', os.path.join(target.subdir, target.get_filename())] + args += rustc.get_output_args(os.path.join(target.subdir, target.get_filename())) + args += self.environment.coredata.get_external_args(target.for_machine, rustc.language) orderdeps = [os.path.join(t.subdir, t.get_filename()) for t in target.link_targets] linkdirs = OrderedDict() for d in target.link_targets: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 78292f2..36d4e19 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1359,11 +1359,12 @@ You probably should put it in link_with instead.''') for link_target in self.link_targets: if isinstance(link_target, SharedModule): if self.environment.machines[self.for_machine].is_darwin(): - raise MesonException('''target links against shared modules. -This is not permitted on OSX''') + raise MesonException( + 'target links against shared modules. This is not permitted on OSX') else: - mlog.warning('''target links against shared modules. This is not -recommended as it is not supported on some platforms''') + mlog.warning('target links against shared modules. This ' + 'is not recommended as it is not supported on some ' + 'platforms') return class Generator: diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 95c4698..985f6f3 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -1,4 +1,4 @@ -# Copyright 2012-2017 The Meson development team +# Copyright 2012-2020 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -95,28 +95,32 @@ class CCompiler(CLikeCompiler, Compiler): return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) + def get_options(self) -> 'OptionDictType': + opts = super().get_options() + opts.update({ + 'std': coredata.UserComboOption( + 'C langauge standard to use', + ['none'], + 'none', + ) + }) + return opts -class ClangCCompiler(ClangCompiler, CCompiler): + +class _ClangCStds(CompilerMixinBase): + + """Mixin class for clang based compilers for setting C standards. + + This is used by both ClangCCompiler and ClangClCompiler, as they share + the same versions + """ _C17_VERSION = '>=6.0.0' _C18_VERSION = '>=8.0.0' _C2X_VERSION = '>=9.0.0' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, - linker: T.Optional['DynamicLinker'] = None, - defines: T.Optional[T.Dict[str, str]] = None, - full_version: T.Optional[str] = None): - CCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, linker=linker, full_version=full_version) - ClangCompiler.__init__(self, defines) - default_warn_args = ['-Wall', '-Winvalid-pch'] - self.warn_args = {'0': [], - '1': default_warn_args, - '2': default_warn_args + ['-Wextra'], - '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': - opts = CCompiler.get_options(self) + opts = super().get_options() c_stds = ['c89', 'c99', 'c11'] g_stds = ['gnu89', 'gnu99', 'gnu11'] # https://releases.llvm.org/6.0.0/tools/clang/docs/ReleaseNotes.html @@ -130,13 +134,27 @@ class ClangCCompiler(ClangCompiler, CCompiler): if version_compare(self.version, self._C2X_VERSION): c_stds += ['c2x'] g_stds += ['gnu2x'] - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - ['none'] + c_stds + g_stds, - 'none', - ), - }) + opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore + return opts + + +class ClangCCompiler(_ClangCStds, ClangCompiler, CCompiler): + + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, + info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None, + linker: T.Optional['DynamicLinker'] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + CCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, linker=linker, full_version=full_version) + ClangCompiler.__init__(self, defines) + default_warn_args = ['-Wall', '-Winvalid-pch'] + self.warn_args = {'0': [], + '1': default_warn_args, + '2': default_warn_args + ['-Wextra'], + '3': default_warn_args + ['-Wextra', '-Wpedantic']} + + def get_options(self) -> 'OptionDictType': + opts = super().get_options() if self.info.is_windows() or self.info.is_cygwin(): opts.update({ 'winlibs': coredata.UserArrayOption( @@ -207,13 +225,7 @@ class ArmclangCCompiler(ArmclangCompiler, CCompiler): def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11'], - 'none', - ), - }) + opts['std'].choices = ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -255,13 +267,7 @@ class GnuCCompiler(GnuCompiler, CCompiler): if version_compare(self.version, self._C2X_VERSION): c_stds += ['c2x'] g_stds += ['gnu2x'] - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - ['none'] + c_stds + g_stds, - 'none', - ), - }) + opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore if self.info.is_windows() or self.info.is_cygwin(): opts.update({ 'winlibs': coredata.UserArrayOption( @@ -327,17 +333,11 @@ class ElbrusCCompiler(GnuCCompiler, ElbrusCompiler): # It does support some various ISO standards and c/gnu 90, 9x, 1x in addition to those which GNU CC supports. def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - [ - 'none', 'c89', 'c90', 'c9x', 'c99', 'c1x', 'c11', - 'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11', - 'iso9899:2011', 'iso9899:1990', 'iso9899:199409', 'iso9899:1999', - ], - 'none', - ), - }) + opts['std'].choices = [ # type: ignore + 'none', 'c89', 'c90', 'c9x', 'c99', 'c1x', 'c11', + 'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11', + 'iso9899:2011', 'iso9899:1990', 'iso9899:199409', 'iso9899:1999', + ] return opts # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error. @@ -374,13 +374,7 @@ class IntelCCompiler(IntelGnuLikeCompiler, CCompiler): g_stds = ['gnu89', 'gnu99'] if version_compare(self.version, '>=16.0.0'): c_stds += ['c11'] - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - ['none'] + c_stds + g_stds, - 'none', - ), - }) + opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -416,6 +410,9 @@ class VisualStudioLikeCCompilerMixin(CompilerMixinBase): class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompiler): + _C11_VERSION = '>=19.28' + _C17_VERSION = '>=19.28' + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', target: str, exe_wrapper: T.Optional['ExternalProgram'] = None, @@ -428,26 +425,37 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi def get_options(self) -> 'OptionDictType': opts = super().get_options() - c_stds = ['none', 'c89', 'c99', 'c11'] - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - c_stds, - 'none', - ), - }) + c_stds = ['c89', 'c99'] + # Need to have these to be compatible with projects + # that set c_std to e.g. gnu99. + # https://github.com/mesonbuild/meson/issues/7611 + g_stds = ['gnu89', 'gnu90', 'gnu9x', 'gnu99'] + if version_compare(self.version, self._C11_VERSION): + c_stds += ['c11'] + g_stds += ['gnu1x', 'gnu11'] + if version_compare(self.version, self._C17_VERSION): + c_stds += ['c17', 'c18'] + g_stds += ['gnu17', 'gnu18'] + opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: args = [] std = options['std'] - # As of MVSC 16.7, /std:c11 is the only valid C standard option. - if std.value in {'c11'}: - args.append('/std:' + std.value) + if std.value.startswith('gnu'): + mlog.log_once( + 'cl.exe does not actually support gnu standards, and meson ' + 'will instead demote to the nearest ISO C standard. This ' + 'may cause compilation to fail.') + # As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options. + if std.value in {'c11', 'gnu1x', 'gnu11'}: + args.append('/std:c11') + elif std.value in {'c17', 'c18', 'gnu17', 'gnu18'}: + args.append('/std:c17') return args -class ClangClCCompiler(ClangClCompiler, VisualStudioLikeCCompilerMixin, CCompiler): +class ClangClCCompiler(_ClangCStds, ClangClCompiler, VisualStudioLikeCCompilerMixin, CCompiler): def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', target: str, exe_wrapper: T.Optional['ExternalProgram'] = None, @@ -458,6 +466,12 @@ class ClangClCCompiler(ClangClCompiler, VisualStudioLikeCCompilerMixin, CCompile full_version=full_version) ClangClCompiler.__init__(self, target) + def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + std = options['std'].value + if std != "none": + return ['/clang:-std={}'.format(std)] + return [] + class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerMixin, CCompiler): @@ -475,21 +489,14 @@ class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerM def get_options(self) -> 'OptionDictType': opts = super().get_options() - c_stds = ['none', 'c89', 'c99', 'c11'] - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - c_stds, - 'none', - ), - }) + opts['std'].choices = ['none', 'c89', 'c99', 'c11'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: args = [] std = options['std'] if std.value == 'c89': - mlog.warning("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.", once=True) + mlog.log_once("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.") elif std.value != 'none': args.append('/Qstd:' + std.value) return args @@ -508,13 +515,7 @@ class ArmCCompiler(ArmCompiler, CCompiler): def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - ['none', 'c90', 'c99'], - 'none', - ), - }) + opts['std'].choices = ['none', 'c89', 'c99', 'c11'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -541,13 +542,7 @@ class CcrxCCompiler(CcrxCompiler, CCompiler): def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({ - 'std': coredata.UserComboOption( - 'C language standard to use', - ['none', 'c89', 'c99'], - 'none', - ), - }) + opts['std'].choices = ['none', 'c89', 'c99'] # type: ignore return opts def get_no_stdinc_args(self) -> T.List[str]: @@ -592,9 +587,7 @@ class Xc16CCompiler(Xc16Compiler, CCompiler): def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({'c_std': coredata.UserComboOption('C language standard to use', - ['none', 'c89', 'c99', 'gnu89', 'gnu99'], - 'none')}) + opts['std'].choices = ['none', 'c89', 'c99', 'gnu89', 'gnu99'] # type: ignore return opts def get_no_stdinc_args(self) -> T.List[str]: @@ -637,9 +630,7 @@ class CompCertCCompiler(CompCertCompiler, CCompiler): def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({'c_std': coredata.UserComboOption('C language standard to use', - ['none', 'c89', 'c99'], - 'none')}) + opts['std'].choices = ['none', 'c89', 'c99'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -675,9 +666,7 @@ class C2000CCompiler(C2000Compiler, CCompiler): def get_options(self) -> 'OptionDictType': opts = CCompiler.get_options(self) - opts.update({'c_std': coredata.UserComboOption('C language standard to use', - ['none', 'c89', 'c99', 'c11'], - 'none')}) + opts['std'].choices = ['none', 'c89', 'c99', 'c11'] # type: ignore return opts def get_no_stdinc_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 0f074bb..4e9b86b 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -785,7 +785,7 @@ class Compiler(metaclass=abc.ABCMeta): # On Windows antivirus programs and the like hold on to files so # they can't be deleted. There's not much to do in this case. Also, # catch OSError because the directory is then no longer empty. - yield None + return @contextlib.contextmanager def cached_compile(self, code: str, cdata: coredata.CoreData, *, diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 3e9764a..6ba87eb 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -167,6 +167,17 @@ class CPPCompiler(CLikeCompiler, Compiler): raise MesonException('C++ Compiler does not support -std={}'.format(cpp_std)) + def get_options(self) -> 'OptionDictType': + opts = super().get_options() + opts.update({ + 'std': coredata.UserComboOption( + 'C++ language standard to use', + ['none'], + 'none', + ), + }) + return opts + class ClangCPPCompiler(ClangCompiler, CPPCompiler): def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, @@ -192,13 +203,11 @@ class ClangCPPCompiler(ClangCompiler, CPPCompiler): 'default', ), 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'std': coredata.UserComboOption( - 'C++ language standard to use', - ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', - 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a'], - 'none', - ), }) + opts['std'].choices = [ # type: ignore + 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'c++2a', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a', + ] if self.info.is_windows() or self.info.is_cygwin(): opts.update({ 'winlibs': coredata.UserArrayOption( @@ -283,15 +292,11 @@ class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'std': coredata.UserComboOption( - 'C++ language standard to use', - [ - 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', - 'gnu++98', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', - ], - 'none', - ), }) + opts['std'].choices = [ # type: ignore + 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'gnu++98', + 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', + ] return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -332,17 +337,16 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): 'default', ), 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'std': coredata.UserComboOption( - 'C++ language standard to use', - ['none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', - 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a'], - 'none', - ), 'debugstl': coredata.UserBooleanOption( 'STL debug mode', False, ) }) + opts['std'].choices = [ # type: ignore + 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'c++2a', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', + 'gnu++2a', + ] if self.info.is_windows() or self.info.is_cygwin(): opts.update({ 'winlibs': coredata.UserArrayOption( @@ -437,16 +441,12 @@ class ElbrusCPPCompiler(GnuCPPCompiler, ElbrusCompiler): ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'std': coredata.UserComboOption( - 'C++ language standard to use', - cpp_stds, - 'none', - ), 'debugstl': coredata.UserBooleanOption( 'STL debug mode', False, ), }) + opts['std'].choices = cpp_stds # type: ignore return opts # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error. @@ -515,13 +515,9 @@ class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): 'default', ), 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'std': coredata.UserComboOption( - 'C++ language standard to use', - ['none'] + c_stds + g_stds, - 'none', - ), 'debugstl': coredata.UserBooleanOption('STL debug mode', False), }) + opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -573,16 +569,12 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): 'default', ), 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'std': coredata.UserComboOption( - 'C++ language standard to use', - cpp_stds, - 'none', - ), 'winlibs': coredata.UserArrayOption( 'Windows libs to link against.', msvc_winlibs, ), }) + opts['std'].choices = cpp_stds # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -726,13 +718,7 @@ class ArmCPPCompiler(ArmCompiler, CPPCompiler): def get_options(self) -> 'OptionDictType': opts = CPPCompiler.get_options(self) - opts.update({ - 'std': coredata.UserComboOption( - 'C++ language standard to use', - ['none', 'c++03', 'c++11'], - 'none', - ), - }) + opts['std'].choices = ['none', 'c++03', 'c++11'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -790,9 +776,7 @@ class C2000CPPCompiler(C2000Compiler, CPPCompiler): def get_options(self) -> 'OptionDictType': opts = CPPCompiler.get_options(self) - opts.update({'cpp_std': coredata.UserComboOption('C++ language standard to use', - ['none', 'c++03'], - 'none')}) + opts['std'].choices = ['none', 'c++03'] # type: ignore return opts def get_always_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 603ad13..89fcf40 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -197,9 +197,9 @@ class CudaCompiler(Compiler): def get_options(self) -> 'OptionDictType': opts = super().get_options() - opts.update({'cuda_std': coredata.UserComboOption('C++ language standard to use', - ['none', 'c++03', 'c++11', 'c++14'], - 'none')}) + opts.update({'std': coredata.UserComboOption('C++ language standard to use with cuda', + ['none', 'c++03', 'c++11', 'c++14'], + 'none')}) return opts def _to_host_compiler_options(self, options: 'OptionDictType') -> 'OptionDictType': @@ -212,7 +212,7 @@ class CudaCompiler(Compiler): # the combination of CUDA version and MSVC version; the --std= is thus ignored # and attempting to use it will result in a warning: https://stackoverflow.com/a/51272091/741027 if not is_windows(): - std = options['cuda_std'] + std = options['std'] if std.value != 'none': args.append('--std=' + std.value) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index e85ee6d..036369a 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -150,6 +150,17 @@ class FortranCompiler(CLikeCompiler, Compiler): def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: return self._has_multi_link_arguments(args, env, 'stop; end program') + def get_options(self) -> 'OptionDictType': + opts = super().get_options() + opts.update({ + 'std': coredata.UserComboOption( + 'Fortran language standard to use', + ['none'], + 'none', + ), + }) + return opts + class GnuFortranCompiler(GnuCompiler, FortranCompiler): @@ -175,13 +186,7 @@ class GnuFortranCompiler(GnuCompiler, FortranCompiler): fortran_stds += ['f2008'] if version_compare(self.version, '>=8.0.0'): fortran_stds += ['f2018'] - opts.update({ - 'std': coredata.UserComboOption( - 'Fortran language standard to use', - ['none'] + fortran_stds, - 'none', - ), - }) + opts['std'].choices = ['none'] + fortran_stds # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -310,14 +315,7 @@ class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler): def get_options(self) -> 'OptionDictType': opts = FortranCompiler.get_options(self) - fortran_stds = ['legacy', 'f95', 'f2003', 'f2008', 'f2018'] - opts.update({ - 'std': coredata.UserComboOption( - 'Fortran language standard to use', - ['none'] + fortran_stds, - 'none', - ), - }) + opts['std'].choices = ['legacy', 'f95', 'f2003', 'f2008', 'f2018'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: @@ -367,14 +365,7 @@ class IntelClFortranCompiler(IntelVisualStudioLikeCompiler, FortranCompiler): def get_options(self) -> 'OptionDictType': opts = FortranCompiler.get_options(self) - fortran_stds = ['legacy', 'f95', 'f2003', 'f2008', 'f2018'] - opts.update({ - 'std': coredata.UserComboOption( - 'Fortran language standard to use', - ['none'] + fortran_stds, - 'none', - ), - }) + opts['std'].choices = ['legacy', 'f95', 'f2003', 'f2008', 'f2018'] # type: ignore return opts def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 87bc40c..cd18b35 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -50,7 +50,7 @@ class EmscriptenMixin(Compiler): def thread_link_flags(self, env: 'Environment') -> T.List[str]: args = ['-s', 'USE_PTHREADS=1'] - count = env.coredata.compiler_options[self.for_machine]['{}_thread_count'.format(self.language)].value # type: int + count = env.coredata.compiler_options[self.for_machine][self.language]['thread_count'].value # type: int if count: args.extend(['-s', 'PTHREAD_POOL_SIZE={}'.format(count)]) return args @@ -58,7 +58,7 @@ class EmscriptenMixin(Compiler): def get_options(self) -> 'coredata.OptionDictType': opts = super().get_options() opts.update({ - '{}_thread_count'.format(self.language): coredata.UserIntegerOption( + 'thread_count': coredata.UserIntegerOption( 'Number of threads to use in web assembly, set to 0 to disable', (0, None, 4), # Default was picked at random ), diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 469859b..312b3b6 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -17,7 +17,7 @@ import textwrap import typing as T from .. import coredata -from ..mesonlib import EnvironmentException, MachineChoice, Popen_safe +from ..mesonlib import EnvironmentException, MachineChoice, MesonException, Popen_safe from .compilers import Compiler, rust_buildtype_args, clike_debug_args if T.TYPE_CHECKING: @@ -52,6 +52,9 @@ class RustCompiler(Compiler): linker=linker) self.exe_wrapper = exe_wrapper self.id = 'rustc' + self.base_options.append('b_colorout') + if 'link' in self.linker.id: + self.base_options.append('b_vscrt') def needs_static_linker(self) -> bool: return False @@ -122,6 +125,10 @@ class RustCompiler(Compiler): def get_output_args(self, outputname: str) -> T.List[str]: return ['-o', outputname] + @classmethod + def use_linker_args(cls, linker: str) -> T.List[str]: + return ['-C', 'linker={}'.format(linker)] + # Rust does not have a use_linker_args because it dispatches to a gcc-like # C compiler for dynamic linking, as such we invoke the C compiler's # use_linker_args method instead. @@ -141,3 +148,12 @@ class RustCompiler(Compiler): if std.value != 'none': args.append('--edition=' + std.value) return args + + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + # Rust handles this for us, we don't need to do anything + return [] + + def get_colorout_args(self, colortype: str) -> T.List[str]: + if colortype in {'always', 'never', 'auto'}: + return [f'--color={colortype}'] + raise MesonException(f'Invalid color type for rust {colortype}') diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 3a5f5f8..87518e1 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -869,9 +869,13 @@ class PkgConfigDependency(ExternalDependency): def _set_libs(self): env = None - libcmd = [self.name, '--libs'] + libcmd = ['--libs'] + if self.static: libcmd.append('--static') + + libcmd.append(self.name) + # Force pkg-config to output -L fields even if they are system # paths so we can do manual searching with cc.find_library() later. env = os.environ.copy() diff --git a/mesonbuild/depfile.py b/mesonbuild/depfile.py index 7a896cd..62cbe81 100644 --- a/mesonbuild/depfile.py +++ b/mesonbuild/depfile.py @@ -82,4 +82,4 @@ class DepFile: deps.update(target.deps) for dep in target.deps: deps.update(self.get_all_dependencies(dep, visited)) - return deps + return sorted(deps) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 588005b..7194d03 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -129,6 +129,9 @@ from .compilers import ( VisualStudioCPPCompiler, ) +if T.TYPE_CHECKING: + from .dependencies import ExternalProgram + build_filename = 'meson.build' CompilersDict = T.Dict[str, Compiler] @@ -869,7 +872,7 @@ class Environment: defines[rest[0]] = rest[1] return defines - def _get_compilers(self, lang, for_machine): + def _get_compilers(self, lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[T.List[str]], T.List[str], T.Optional['ExternalProgram']]: ''' The list of compilers is detected in the exact same way for C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. @@ -1062,9 +1065,17 @@ class Environment: self.__failed_to_detect_linker(compiler, check_args, o, e) return linker - def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice) -> Compiler: + def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice, *, override_compiler: T.Optional[T.List[str]] = None) -> Compiler: + """Shared implementation for finding the C or C++ compiler to use. + + the override_compiler option is provided to allow compilers which use + the compiler (GCC or Clang usually) as their shared linker, to find + the linker they need. + """ popen_exceptions = {} compilers, ccache, exe_wrap = self._get_compilers(lang, for_machine) + if override_compiler is not None: + compilers = [override_compiler] is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] @@ -1619,9 +1630,9 @@ class Environment: return comp_class(exelist, version, for_machine, info, is_cross) raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - def detect_rust_compiler(self, for_machine): - popen_exceptions = {} - compilers, ccache, exe_wrap = self._get_compilers('rust', for_machine) + def detect_rust_compiler(self, for_machine: MachineChoice) -> RustCompiler: + popen_exceptions = {} # type: T.Dict[str, Exception] + compilers, _, exe_wrap = self._get_compilers('rust', for_machine) is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] @@ -1634,7 +1645,7 @@ class Environment: compiler = [compiler] arg = ['--version'] try: - p, out = Popen_safe(compiler + arg)[0:2] + out = Popen_safe(compiler + arg)[1] except OSError as e: popen_exceptions[' '.join(compiler + arg)] = e continue @@ -1651,17 +1662,30 @@ class Environment: # the default use that, and second add the necessary arguments # to rust to use -fuse-ld + if any(a.startswith('linker=') for a in compiler): + mlog.warning( + 'Please do not put -C linker= in your compiler ' + 'command, set rust_ld=command in your cross file ' + 'or use the RUST_LD environment variable. meson ' + 'will override your seletion otherwise.') + if override is None: extra_args = {} always_args = [] if is_link_exe: - compiler.extend(['-C', 'linker={}'.format(cc.linker.exelist[0])]) + compiler.extend(RustCompiler.use_linker_args(cc.linker.exelist[0])) extra_args['direct'] = True extra_args['machine'] = cc.linker.machine - elif not ((info.is_darwin() and isinstance(cc, AppleClangCCompiler)) or - isinstance(cc, GnuCCompiler)): - c = cc.exelist[1] if cc.exelist[0].endswith('ccache') else cc.exelist[0] - compiler.extend(['-C', 'linker={}'.format(c)]) + else: + exelist = cc.linker.exelist.copy() + if 'ccache' in exelist[0]: + del exelist[0] + c = exelist.pop(0) + compiler.extend(RustCompiler.use_linker_args(c)) + + # Also ensure that we pass any extra arguments to the linker + for l in exelist: + compiler.extend(['-C', 'link-arg={}'.format(l)]) # This trickery with type() gets us the class of the linker # so we can initialize a new copy for the Rust Compiler @@ -1675,21 +1699,22 @@ class Environment: elif 'link' in override[0]: linker = self._guess_win_linker( override, RustCompiler, for_machine, use_linker_prefix=False) + # rustc takes linker arguments without a prefix, and + # inserts the correct prefix itself. linker.direct = True + compiler.extend(RustCompiler.use_linker_args(linker.exelist[0])) else: - # We're creating a new type of "C" compiler, that has rust - # as it's language. This is gross, but I can't figure out - # another way to handle this, because rustc is actually - # invoking the c compiler as it's linker. - b = type('b', (type(cc), ), {}) - b.language = RustCompiler.language - linker = self._guess_nix_linker(cc.exelist, b, for_machine) + # On linux and macos rust will invoke the c compiler for + # linking, on windows it will use lld-link or link.exe. + # we will simply ask for the C compiler that coresponds to + # it, and use that. + cc = self._detect_c_or_cpp_compiler('c', for_machine, override_compiler=override) + linker = cc.linker # Of course, we're not going to use any of that, we just # need it to get the proper arguments to pass to rustc - c = cc.exelist[1] if cc.exelist[0].endswith('ccache') else cc.exelist[0] - compiler.extend(['-C', 'linker={}'.format(c)]) - compiler.extend(['-C', 'link-args={}'.format(' '.join(cc.use_linker_args(override[0])))]) + c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] + compiler.extend(RustCompiler.use_linker_args(c)) self.coredata.add_lang_args(RustCompiler.language, RustCompiler, for_machine, self) return RustCompiler( diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 28ac74f..6896a4d 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -308,6 +308,7 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): 'set_quoted': self.set_quoted_method, 'has': self.has_method, 'get': self.get_method, + 'keys': self.keys_method, 'get_unquoted': self.get_unquoted_method, 'merge_from': self.merge_from_method, }) @@ -401,6 +402,10 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): def get(self, name): return self.held_object.values[name] # (val, desc) + @FeatureNew('configuration_data.keys()', '0.57.0') + def keys_method(self, args, kwargs): + return sorted(self.keys()) + def keys(self): return self.held_object.values.keys() @@ -3170,6 +3175,8 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.project_name = proj_name self.active_projectname = proj_name self.project_version = kwargs.get('version', 'undefined') + if not isinstance(self.project_version, str): + raise InvalidCode('The version keyword argument must be a string.') if self.build.project_version is None: self.build.project_version = self.project_version proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) @@ -4636,7 +4643,11 @@ different subdirectory. self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs) def warn_about_builtin_args(self, args): - warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic') + # -Wpedantic is deliberately not included, since some people want to use it but not use -Wextra + # see e.g. + # https://github.com/mesonbuild/meson/issues/3275#issuecomment-641354956 + # https://github.com/mesonbuild/meson/issues/3742 + warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra') optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os') for arg in args: if arg in warnargs: @@ -4645,6 +4656,9 @@ different subdirectory. elif arg in optargs: mlog.warning('Consider using the built-in optimization level instead of using "{}".'.format(arg), location=self.current_node) + elif arg == '-Werror': + mlog.warning('Consider using the built-in werror option instead of using "{}".'.format(arg), + location=self.current_node) elif arg == '-g': mlog.warning('Consider using the built-in debug option instead of using "{}".'.format(arg), location=self.current_node) diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index d3f8181..e57580b 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -823,7 +823,7 @@ The result of this is undefined and will become a hard error in a future Meson r elif isinstance(items, dict): if len(node.varnames) != 2: raise InvalidArguments('Foreach on dict unpacks key and value') - for key, value in items.items(): + for key, value in sorted(items.items()): self.set_variable(node.varnames[0], key) self.set_variable(node.varnames[1], value) try: @@ -1166,7 +1166,7 @@ The result of this is undefined and will become a hard error in a future Meson r if method_name == 'keys': if len(posargs) != 0: raise InterpreterException('keys() takes no arguments.') - return list(obj.keys()) + return sorted(obj.keys()) raise InterpreterException('Dictionaries do not have a method called "%s".' % method_name) diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index eefb308..46e2de1 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -124,7 +124,7 @@ class AnsiDecorator: def get_text(self, with_codes: bool) -> str: text = self.text - if with_codes: + if with_codes and self.code: text = self.code + self.text + AnsiDecorator.plain_code if self.quoted: text = '"{}"'.format(text) @@ -133,6 +133,9 @@ class AnsiDecorator: def bold(text: str, quoted: bool = False) -> AnsiDecorator: return AnsiDecorator(text, "\033[1m", quoted=quoted) +def plain(text: str) -> AnsiDecorator: + return AnsiDecorator(text, "") + def red(text: str) -> AnsiDecorator: return AnsiDecorator(text, "\033[1;31m") diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 2d32328..547aff1 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -554,15 +554,16 @@ class GnomeModule(ExtensionModule): else: # Because of https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/72 # we can't use the full path until this is merged. + libpath = os.path.join(girtarget.get_subdir(), girtarget.get_filename()) if isinstance(girtarget, build.SharedLibrary): + # need to put our output directory first as we need to use the + # generated libraries instead of any possibly installed system/prefix + # ones. + ret += ["-L@BUILD_ROOT@/{}".format(os.path.dirname(libpath))] libname = girtarget.get_basename() else: - libname = os.path.join("@PRIVATE_OUTDIR_ABS_%s@" % girtarget.get_id(), girtarget.get_filename()) + libname = os.path.join("@BUILD_ROOT@/{}".format(libpath)) ret += ['--library', libname] - # need to put our output directory first as we need to use the - # generated libraries instead of any possibly installed system/prefix - # ones. - ret += ["-L@PRIVATE_OUTDIR_ABS_%s@" % girtarget.get_id()] # Needed for the following binutils bug: # https://github.com/mesonbuild/meson/issues/1911 # However, g-ir-scanner does not understand -Wl,-rpath @@ -1429,7 +1430,7 @@ class GnomeModule(ExtensionModule): GType %s@enum_name@_get_type (void) { - static volatile gsize gtype_id = 0; + static gsize gtype_id = 0; static const G@Type@Value values[] = {''' % func_prefix c_file_kwargs['vprod'] = ' { C_@TYPE@(@VALUENAME@), "@VALUENAME@", "@valuenick@" },' diff --git a/mesonbuild/modules/sourceset.py b/mesonbuild/modules/sourceset.py index e23e12e..e49a548 100644 --- a/mesonbuild/modules/sourceset.py +++ b/mesonbuild/modules/sourceset.py @@ -14,7 +14,7 @@ from collections import namedtuple from .. import mesonlib -from ..mesonlib import listify +from ..mesonlib import listify, OrderedSet from . import ExtensionModule from ..interpreterbase import ( noPosargs, noKwargs, permittedKwargs, @@ -111,7 +111,7 @@ class SourceSetHolder(MutableInterpreterObject, ObjectHolder): def collect(self, enabled_fn, all_sources, into=None): if not into: - into = SourceFiles(set(), set()) + into = SourceFiles(OrderedSet(), OrderedSet()) for entry in self.held_object: if all(x.found() for x in entry.dependencies) and \ all(enabled_fn(key) for key in entry.keys): diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index fc8c033..95e6b79 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -181,7 +181,7 @@ class MesonApp: else: logger_fun = mlog.debug build_machine = intr.builtin['build_machine'] - host_machine = intr.builtin['build_machine'] + host_machine = intr.builtin['host_machine'] target_machine = intr.builtin['target_machine'] assert isinstance(build_machine, interpreter.MachineHolder) assert isinstance(host_machine, interpreter.MachineHolder) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 35cb10a..5804303 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -15,10 +15,10 @@ # A tool to run tests in many different ways. from ._pathlib import Path -from collections import namedtuple +from collections import deque, namedtuple from copy import deepcopy import argparse -import concurrent.futures as conc +import asyncio import datetime import enum import io @@ -167,6 +167,7 @@ class TestResult(enum.Enum): OK = 'OK' TIMEOUT = 'TIMEOUT' + INTERRUPT = 'INTERRUPT' SKIP = 'SKIP' FAIL = 'FAIL' EXPECTEDFAIL = 'EXPECTEDFAIL' @@ -326,7 +327,6 @@ class TAPParser: yield self.Error('Too many tests run (expected {}, got {})'.format(plan.count, num_tests)) - class JunitBuilder: """Builder for Junit test results. @@ -376,7 +376,8 @@ class JunitBuilder: 'testsuite', name=suitename, tests=str(len(test.results)), - errors=str(sum(1 for r in test.results if r is TestResult.ERROR)), + errors=str(sum(1 for r in test.results if r in + {TestResult.INTERRUPT, 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)), @@ -395,6 +396,9 @@ class JunitBuilder: elif result is TestResult.UNEXPECTEDPASS: fail = et.SubElement(testcase, 'failure') fail.text = 'Test unexpected passed.' + elif result is TestResult.INTERRUPT: + fail = et.SubElement(testcase, 'failure') + fail.text = 'Test was interrupted by user.' elif result is TestResult.TIMEOUT: fail = et.SubElement(testcase, 'failure') fail.text = 'Test did not finish before configured timeout.' @@ -606,6 +610,31 @@ def load_tests(build_dir: str) -> T.List[TestSerialisation]: objs = check_testdata(pickle.load(f)) return objs +# Custom waiting primitives for asyncio + +async def try_wait_one(*awaitables: T.Any, timeout: T.Optional[T.Union[int, float]]) -> None: + try: + await asyncio.wait(awaitables, + timeout=timeout, return_when=asyncio.FIRST_COMPLETED) + except asyncio.TimeoutError: + pass + +async def complete(future: asyncio.Future) -> None: + """Wait for completion of the given future, ignoring cancellation.""" + try: + await future + except asyncio.CancelledError: + pass + +async def complete_all(futures: T.Iterable[asyncio.Future]) -> None: + """Wait for completion of all the given futures, ignoring cancellation.""" + while futures: + done, futures = await asyncio.wait(futures, return_when=asyncio.FIRST_EXCEPTION) + # Raise exceptions if needed for all the "done" futures + for f in done: + if not f.cancelled(): + f.result() + class SingleTestRunner: @@ -636,7 +665,7 @@ class SingleTestRunner: return self.test.exe_runner.get_command() + self.test.fname return self.test.fname - def run(self) -> TestRun: + async def run(self) -> TestRun: cmd = self._get_cmd() if cmd is None: skip_stdout = 'Not run because can not execute cross compiled binaries.' @@ -645,9 +674,94 @@ class SingleTestRunner: wrap = TestHarness.get_wrapper(self.options) if self.options.gdb: self.test.timeout = None - return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args) + return await self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args) + + async def _run_subprocess(self, args: T.List[str], *, timeout: T.Optional[int], + stdout: T.IO, stderr: T.IO, + env: T.Dict[str, str], cwd: T.Optional[str]) -> T.Tuple[int, TestResult, T.Optional[str]]: + async def kill_process(p: asyncio.subprocess.Process) -> T.Optional[str]: + # Python does not provide multiplatform support for + # killing a process and all its children so we need + # to roll our own. + try: + if is_windows(): + subprocess.run(['taskkill', '/F', '/T', '/PID', str(p.pid)]) + else: + # Send a termination signal to the process group that setsid() + # created - giving it a chance to perform any cleanup. + os.killpg(p.pid, signal.SIGTERM) + + # Make sure the termination signal actually kills the process + # group, otherwise retry with a SIGKILL. + await try_wait_one(p.wait(), timeout=0.5) + if p.returncode is not None: + return None + + os.killpg(p.pid, signal.SIGKILL) + + await try_wait_one(p.wait(), timeout=1) + if p.returncode is not None: + return None - def _run_cmd(self, cmd: T.List[str]) -> TestRun: + # An earlier kill attempt has not worked for whatever reason. + # Try to kill it one last time with a direct call. + # If the process has spawned children, they will remain around. + p.kill() + await try_wait_one(p.wait(), timeout=1) + if p.returncode is not None: + return None + return 'Test process could not be killed.' + except ProcessLookupError: + # Sometimes (e.g. with Wine) this happens. There's nothing + # we can do, probably the process already died so just wait + # for the event loop to pick that up. + await p.wait() + return None + + # Let gdb handle ^C instead of us + if self.options.gdb: + previous_sigint_handler = signal.getsignal(signal.SIGINT) + # Make the meson executable ignore SIGINT while gdb is running. + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def preexec_fn() -> None: + if self.options.gdb: + # Restore the SIGINT handler for the child process to + # ensure it can handle it. + signal.signal(signal.SIGINT, signal.SIG_DFL) + else: + # We don't want setsid() in gdb because gdb needs the + # terminal in order to handle ^C and not show tcsetpgrp() + # errors avoid not being able to use the terminal. + os.setsid() + + p = await asyncio.create_subprocess_exec(*args, + stdout=stdout, + stderr=stderr, + env=env, + cwd=cwd, + preexec_fn=preexec_fn if not is_windows() else None) + result = None + additional_error = None + try: + await try_wait_one(p.wait(), timeout=timeout) + if p.returncode is None: + if self.options.verbose: + print('{} time out (After {} seconds)'.format(self.test.name, timeout)) + additional_error = await kill_process(p) + result = TestResult.TIMEOUT + except asyncio.CancelledError: + # The main loop must have seen Ctrl-C. + additional_error = await kill_process(p) + result = TestResult.INTERRUPT + finally: + if self.options.gdb: + # Let us accept ^C again + signal.signal(signal.SIGINT, previous_sigint_handler) + + return p.returncode or 0, result, additional_error + + async def _run_cmd(self, cmd: T.List[str]) -> TestRun: starttime = time.time() if self.test.extra_paths: @@ -678,23 +792,6 @@ class SingleTestRunner: if self.test.protocol is TestProtocol.TAP and stderr is stdout: stdout = tempfile.TemporaryFile("wb+") - # Let gdb handle ^C instead of us - if self.options.gdb: - previous_sigint_handler = signal.getsignal(signal.SIGINT) - # Make the meson executable ignore SIGINT while gdb is running. - signal.signal(signal.SIGINT, signal.SIG_IGN) - - def preexec_fn() -> None: - if self.options.gdb: - # Restore the SIGINT handler for the child process to - # ensure it can handle it. - signal.signal(signal.SIGINT, signal.SIG_DFL) - else: - # We don't want setsid() in gdb because gdb needs the - # terminal in order to handle ^C and not show tcsetpgrp() - # errors avoid not being able to use the terminal. - os.setsid() - extra_cmd = [] # type: T.List[str] if self.test.protocol is TestProtocol.GTEST: gtestname = self.test.name @@ -702,77 +799,19 @@ class SingleTestRunner: gtestname = os.path.join(self.test.workdir, self.test.name) extra_cmd.append('--gtest_output=xml:{}.xml'.format(gtestname)) - p = subprocess.Popen(cmd + extra_cmd, - stdout=stdout, - stderr=stderr, - env=self.env, - cwd=self.test.workdir, - preexec_fn=preexec_fn if not is_windows() else None) - timed_out = False - kill_test = False if self.test.timeout is None: timeout = None elif self.options.timeout_multiplier is not None: timeout = self.test.timeout * self.options.timeout_multiplier else: timeout = self.test.timeout - try: - p.communicate(timeout=timeout) - except subprocess.TimeoutExpired: - if self.options.verbose: - print('{} time out (After {} seconds)'.format(self.test.name, timeout)) - timed_out = True - except KeyboardInterrupt: - mlog.warning('CTRL-C detected while running {}'.format(self.test.name)) - kill_test = True - finally: - if self.options.gdb: - # Let us accept ^C again - signal.signal(signal.SIGINT, previous_sigint_handler) - - additional_error = None - - if kill_test or timed_out: - # Python does not provide multiplatform support for - # killing a process and all its children so we need - # to roll our own. - if is_windows(): - subprocess.run(['taskkill', '/F', '/T', '/PID', str(p.pid)]) - else: - def _send_signal_to_process_group(pgid : int, signum : int) -> None: - """ sends a signal to a process group """ - try: - os.killpg(pgid, signum) - except ProcessLookupError: - # Sometimes (e.g. with Wine) this happens. - # There's nothing we can do (maybe the process - # already died) so carry on. - pass - - # Send a termination signal to the process group that setsid() - # created - giving it a chance to perform any cleanup. - _send_signal_to_process_group(p.pid, signal.SIGTERM) - - # Make sure the termination signal actually kills the process - # group, otherwise retry with a SIGKILL. - try: - p.communicate(timeout=0.5) - except subprocess.TimeoutExpired: - _send_signal_to_process_group(p.pid, signal.SIGKILL) - try: - p.communicate(timeout=1) - except subprocess.TimeoutExpired: - # An earlier kill attempt has not worked for whatever reason. - # Try to kill it one last time with a direct call. - # If the process has spawned children, they will remain around. - p.kill() - try: - p.communicate(timeout=1) - except subprocess.TimeoutExpired: - additional_error = 'Test process could not be killed.' - except ValueError: - additional_error = 'Could not read output. Maybe the process has redirected its stdout/stderr?' + returncode, result, additional_error = await self._run_subprocess(cmd + extra_cmd, + timeout=timeout, + stdout=stdout, + stderr=stderr, + env=self.env, + cwd=self.test.workdir) endtime = time.time() duration = endtime - starttime if additional_error is None: @@ -789,17 +828,17 @@ class SingleTestRunner: else: stdo = "" stde = additional_error - if timed_out: - return TestRun(self.test, self.test_env, TestResult.TIMEOUT, [], p.returncode, starttime, duration, stdo, stde, cmd) + if result: + return TestRun(self.test, self.test_env, result, [], returncode, starttime, duration, stdo, stde, cmd) else: if self.test.protocol is TestProtocol.EXITCODE: - return TestRun.make_exitcode(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd) + return TestRun.make_exitcode(self.test, self.test_env, 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) + return TestRun.make_gtest(self.test, self.test_env, returncode, starttime, duration, stdo, stde, cmd) else: if self.options.verbose: print(stdo, end='') - return TestRun.make_tap(self.test, self.test_env, p.returncode, starttime, duration, stdo, stde, cmd) + return TestRun.make_tap(self.test, self.test_env, returncode, starttime, duration, stdo, stde, cmd) class TestHarness: @@ -841,7 +880,7 @@ class TestHarness: def close_logfiles(self) -> None: for f in ['logfile', 'jsonlogfile']: - lfile = getattr(self, f) + lfile = getattr(self, f) if lfile: lfile.close() setattr(self, f, None) @@ -892,7 +931,7 @@ class TestHarness: self.skip_count += 1 elif result.res is TestResult.OK: self.success_count += 1 - elif result.res is TestResult.FAIL or result.res is TestResult.ERROR: + elif result.res in {TestResult.FAIL, TestResult.ERROR, TestResult.INTERRUPT}: self.fail_count += 1 elif result.res is TestResult.EXPECTEDFAIL: self.expectedfail_count += 1 @@ -905,7 +944,7 @@ class TestHarness: tests: T.List[TestSerialisation], name: str, result: TestRun, i: int) -> None: ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL) - bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT, + bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT, TestResult.INTERRUPT, TestResult.UNEXPECTEDPASS, TestResult.ERROR) result_str = '{num:{numlen}}/{testcount} {name:{name_max_len}} {res:{reslen}} {dur:.2f}s'.format( numlen=len(str(test_count)), @@ -918,20 +957,15 @@ class TestHarness: dur=result.duration) if result.res is TestResult.FAIL: result_str += ' ' + returncode_to_status(result.returncode) + if result.res in bad_statuses: + self.collected_failures.append(result_str) if not self.options.quiet or result.res not in ok_statuses: - if result.res not in ok_statuses: - self.collected_failures.append(result_str) - if mlog.colorize_console(): - if result.res in bad_statuses: - self.collected_failures.append(result_str) - decorator = mlog.red - elif result.res is TestResult.SKIP: - decorator = mlog.yellow - else: - sys.exit('Unreachable code was ... well ... reached.') - print(decorator(result_str).get_text(True)) - else: - print(result_str) + decorator = mlog.plain + if result.res in bad_statuses: + decorator = mlog.red + elif result.res is TestResult.SKIP: + decorator = mlog.yellow + print(decorator(result_str).get_text(mlog.colorize_console())) result_str += "\n\n" + result.get_log() if result.res in bad_statuses: if self.options.print_errorlogs: @@ -956,7 +990,7 @@ class TestHarness: Skipped: {:<4} Timeout: {:<4} ''').format(self.success_count, self.expectedfail_count, self.fail_count, - self.unexpectedpass_count, self.skip_count, self.timeout_count) + self.unexpectedpass_count, self.skip_count, self.timeout_count) print(msg) if self.logfile: self.logfile.write(msg) @@ -1136,8 +1170,14 @@ class TestHarness: return test.name def run_tests(self, tests: T.List[TestSerialisation]) -> None: - executor = None - futures = [] # type: T.List[T.Tuple[conc.Future[TestRun], int, int, T.List[TestSerialisation], str, int]] + # Replace with asyncio.run once we can require Python 3.7 + loop = asyncio.get_event_loop() + loop.run_until_complete(self._run_tests(tests)) + + async def _run_tests(self, tests: T.List[TestSerialisation]) -> None: + semaphore = asyncio.Semaphore(self.options.num_processes) + futures = deque() # type: T.Deque[asyncio.Future] + running_tests = dict() # type: T.Dict[asyncio.Future, str] test_count = len(tests) name_max_len = max([len(self.get_pretty_suite(test)) for test in tests]) self.open_log_files() @@ -1145,59 +1185,87 @@ class TestHarness: if self.options.wd: os.chdir(self.options.wd) self.build_data = build.load(os.getcwd()) + interrupted = False + async def run_test(test: SingleTestRunner, + name: str, index: int) -> None: + async with semaphore: + if interrupted or (self.options.repeat > 1 and self.fail_count): + return + res = await test.run() + self.process_test_result(res) + self.print_stats(test_count, name_max_len, tests, name, res, index) + + def test_done(f: asyncio.Future) -> None: + if not f.cancelled(): + f.result() + futures.remove(f) + try: + del running_tests[f] + except KeyError: + pass + + def cancel_one_test(warn: bool) -> None: + future = futures.popleft() + futures.append(future) + if warn: + mlog.warning('CTRL-C detected, interrupting {}'.format(running_tests[future])) + del running_tests[future] + future.cancel() + + def sigterm_handler() -> None: + nonlocal interrupted + if interrupted: + return + interrupted = True + mlog.warning('Received SIGTERM, exiting') + while running_tests: + cancel_one_test(False) + + def sigint_handler() -> None: + # We always pick the longest-running future that has not been cancelled + # If all the tests have been CTRL-C'ed, just stop + nonlocal interrupted + if interrupted: + return + if running_tests: + cancel_one_test(True) + else: + mlog.warning('CTRL-C detected, exiting') + interrupted = True + + if sys.platform != 'win32': + asyncio.get_event_loop().add_signal_handler(signal.SIGINT, sigint_handler) + asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, sigterm_handler) try: for _ in range(self.options.repeat): for i, test in enumerate(tests, 1): visible_name = self.get_pretty_suite(test) single_test = self.get_test_runner(test) - if not test.is_parallel or self.options.num_processes == 1 or single_test.options.gdb: - self.drain_futures(futures) - futures = [] - res = single_test.run() - self.process_test_result(res) - self.print_stats(test_count, name_max_len, tests, visible_name, res, i) - else: - if not executor: - executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes) - f = executor.submit(single_test.run) - futures.append((f, test_count, name_max_len, tests, visible_name, i)) - if self.options.repeat > 1 and self.fail_count: - break + if not test.is_parallel or single_test.options.gdb: + await complete_all(futures) + future = asyncio.ensure_future(run_test(single_test, visible_name, i)) + futures.append(future) + running_tests[future] = visible_name + future.add_done_callback(test_done) + if not test.is_parallel or single_test.options.gdb: + await complete(future) if self.options.repeat > 1 and self.fail_count: break - self.drain_futures(futures) + await complete_all(futures) self.print_collected_logs() self.print_summary() if self.logfilename: print('Full log written to {}'.format(self.logfilename)) finally: + if sys.platform != 'win32': + asyncio.get_event_loop().remove_signal_handler(signal.SIGINT) + asyncio.get_event_loop().remove_signal_handler(signal.SIGTERM) os.chdir(startdir) - def drain_futures(self, futures: T.List[T.Tuple['conc.Future[TestRun]', int, int, T.List[TestSerialisation], str, int]]) -> None: - for x in futures: - (result, test_count, name_max_len, tests, name, i) = x - if self.options.repeat > 1 and self.fail_count: - result.cancel() - if self.options.verbose: - result.result() - self.process_test_result(result.result()) - self.print_stats(test_count, name_max_len, tests, name, result.result(), i) - - def run_special(self) -> int: - '''Tests run by the user, usually something like "under gdb 1000 times".''' - if self.is_run: - raise RuntimeError('Can not use run_special after a full run.') - tests = self.get_tests() - if not tests: - return 0 - self.run_tests(tests) - return self.total_failure_count() - - def list_tests(th: TestHarness) -> bool: tests = th.get_tests() for t in tests: @@ -1240,6 +1308,10 @@ def run(options: argparse.Namespace) -> int: if options.wrapper: check_bin = options.wrapper[0] + if sys.platform == 'win32': + loop = asyncio.ProactorEventLoop() + asyncio.set_event_loop(loop) + if check_bin is not None: exe = ExternalProgram(check_bin, silent=True) if not exe.found(): @@ -1257,9 +1329,7 @@ def run(options: argparse.Namespace) -> int: try: if options.list: return list_tests(th) - if not options.args: - return th.doit() - return th.run_special() + return th.doit() except TestException as e: print('Meson test encountered an error:\n') if os.environ.get('MESON_FORCE_BACKTRACE'): diff --git a/mesonbuild/scripts/clangformat.py b/mesonbuild/scripts/clangformat.py index e7a3ff8..062cb43 100644 --- a/mesonbuild/scripts/clangformat.py +++ b/mesonbuild/scripts/clangformat.py @@ -25,6 +25,7 @@ def clangformat(exelist: T.List[str], srcdir_name: str, builddir_name: str) -> i suffixes = set(lang_suffixes['c']).union(set(lang_suffixes['cpp'])) suffixes.add('h') futures = [] + returncode = 0 with ThreadPoolExecutor() as e: for f in (x for suff in suffixes for x in srcdir.glob('**/*.' + suff)): if f.is_dir(): @@ -32,9 +33,9 @@ def clangformat(exelist: T.List[str], srcdir_name: str, builddir_name: str) -> i strf = str(f) if strf.startswith(builddir_name): continue - futures.append(e.submit(subprocess.check_call, exelist + ['-style=file', '-i', strf])) - [x.result() for x in futures] - return 0 + futures.append(e.submit(subprocess.run, exelist + ['-style=file', '-i', strf])) + returncode = max([x.result().returncode for x in futures]) + return returncode def run(args: T.List[str]) -> int: srcdir_name = args[0] diff --git a/mesonbuild/scripts/clangtidy.py b/mesonbuild/scripts/clangtidy.py index dfaf998..8d366c8 100644 --- a/mesonbuild/scripts/clangtidy.py +++ b/mesonbuild/scripts/clangtidy.py @@ -22,7 +22,7 @@ import typing as T from ..compilers import lang_suffixes -def manual_clangformat(srcdir_name: str, builddir_name: str) -> int: +def manual_clangtidy(srcdir_name: str, builddir_name: str) -> int: srcdir = pathlib.Path(srcdir_name) suffixes = set(lang_suffixes['c']).union(set(lang_suffixes['cpp'])) suffixes.add('h') @@ -36,10 +36,10 @@ def manual_clangformat(srcdir_name: str, builddir_name: str) -> int: if strf.startswith(builddir_name): continue futures.append(e.submit(subprocess.run, ['clang-tidy', '-p', builddir_name, strf])) - [max(returncode, x.result().returncode) for x in futures] + returncode = max([x.result().returncode for x in futures]) return returncode -def clangformat(srcdir_name: str, builddir_name: str) -> int: +def clangtidy(srcdir_name: str, builddir_name: str) -> int: run_clang_tidy = None for rct in ('run-clang-tidy', 'run-clang-tidy.py'): if shutil.which(rct): @@ -49,10 +49,9 @@ def clangformat(srcdir_name: str, builddir_name: str) -> int: return subprocess.run([run_clang_tidy, '-p', builddir_name, '^(?!' + re.escape(builddir_name + os.path.sep) +').*$']).returncode else: print('Could not find run-clang-tidy, running checks manually.') - manual_clangformat(srcdir_name, builddir_name) - return 0 + return manual_clangtidy(srcdir_name, builddir_name) def run(args: T.List[str]) -> int: srcdir_name = args[0] builddir_name = args[1] - return clangformat(srcdir_name, builddir_name) + return clangtidy(srcdir_name, builddir_name) diff --git a/run_project_tests.py b/run_project_tests.py index e4a458e..8bf6437 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -870,6 +870,10 @@ def skippable(suite, test): if test.endswith('15 llvm'): return True + # This test breaks with gobject-introspection <= 1.58.1 + if test.endswith('34 gir static lib'): + return True + # No frameworks test should be skipped on linux CI, as we expect all # prerequisites to be installed if mesonlib.is_linux(): diff --git a/run_unittests.py b/run_unittests.py index f0c2f65..9815058 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from mesonbuild.compilers.objc import AppleClangObjCCompiler import time import stat import subprocess @@ -885,11 +886,11 @@ class InternalTests(unittest.TestCase): def fake_call_pkgbin(self, args, env=None): if '--libs' not in args: return 0, '', '' - if args[0] == 'foo': + if args[-1] == 'foo': return 0, '-L{} -lfoo -L{} -lbar'.format(p2.as_posix(), p1.as_posix()), '' - if args[0] == 'bar': + if args[-1] == 'bar': return 0, '-L{} -lbar'.format(p2.as_posix()), '' - if args[0] == 'internal': + if args[-1] == 'internal': return 0, '-L{} -lpthread -lm -lc -lrt -ldl'.format(p1.as_posix()), '' old_call = PkgConfigDependency._call_pkgbin @@ -1186,7 +1187,7 @@ class InternalTests(unittest.TestCase): ]: d = mesonbuild.depfile.DepFile(f) deps = d.get_all_dependencies(target) - self.assertEqual(deps, expdeps) + self.assertEqual(sorted(deps), sorted(expdeps)) def test_log_once(self): f = io.StringIO() @@ -2942,20 +2943,22 @@ class AllPlatformTests(BasePlatformTests): # the source tree leads to all kinds of trouble. with tempfile.TemporaryDirectory() as project_dir: with open(os.path.join(project_dir, 'meson.build'), 'w') as ofile: - ofile.write('''project('disttest', 'c', version : '1.4.3') -e = executable('distexe', 'distexe.c') -test('dist test', e) -subproject('vcssub', required : false) -subproject('tarballsub', required : false) -''') + ofile.write(textwrap.dedent('''\ + project('disttest', 'c', version : '1.4.3') + e = executable('distexe', 'distexe.c') + test('dist test', e) + subproject('vcssub', required : false) + subproject('tarballsub', required : false) + ''')) with open(os.path.join(project_dir, 'distexe.c'), 'w') as ofile: - ofile.write('''#include<stdio.h> + ofile.write(textwrap.dedent('''\ + #include<stdio.h> -int main(int argc, char **argv) { - printf("I am a distribution test.\\n"); - return 0; -} -''') + int main(int argc, char **argv) { + printf("I am a distribution test.\\n"); + return 0; + } + ''')) xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') xz_checksumfile = xz_distfile + '.sha256sum' zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip') @@ -3614,8 +3617,8 @@ int main(int argc, char **argv) { """ tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking') out = self.init(tdir) - msg = ('''WARNING: target links against shared modules. This is not -recommended as it is not supported on some platforms''') + msg = ('WARNING: target links against shared modules. This is not ' + 'recommended as it is not supported on some platforms') self.assertIn(msg, out) def test_ndebug_if_release_disabled(self): @@ -7264,16 +7267,18 @@ class LinuxlikeTests(BasePlatformTests): testdir = os.path.join(self.unit_test_dir, '61 identity cross') nativefile = tempfile.NamedTemporaryFile(mode='w') - nativefile.write('''[binaries] -c = ['{0}'] -'''.format(os.path.join(testdir, 'build_wrapper.py'))) + nativefile.write(textwrap.dedent('''\ + [binaries] + c = ['{0}'] + '''.format(os.path.join(testdir, 'build_wrapper.py')))) nativefile.flush() self.meson_native_file = nativefile.name crossfile = tempfile.NamedTemporaryFile(mode='w') - crossfile.write('''[binaries] -c = ['{0}'] -'''.format(os.path.join(testdir, 'host_wrapper.py'))) + crossfile.write(textwrap.dedent('''\ + [binaries] + c = ['{0}'] + '''.format(os.path.join(testdir, 'host_wrapper.py')))) crossfile.flush() self.meson_cross_file = crossfile.name @@ -7286,9 +7291,10 @@ c = ['{0}'] 'CC_FOR_BUILD': '"' + os.path.join(testdir, 'build_wrapper.py') + '"', } crossfile = tempfile.NamedTemporaryFile(mode='w') - crossfile.write('''[binaries] -c = ['{0}'] -'''.format(os.path.join(testdir, 'host_wrapper.py'))) + crossfile.write(textwrap.dedent('''\ + [binaries] + c = ['{0}'] + '''.format(os.path.join(testdir, 'host_wrapper.py')))) crossfile.flush() self.meson_cross_file = crossfile.name # TODO should someday be explicit about build platform only here @@ -7334,6 +7340,11 @@ c = ['{0}'] with mock.patch.dict(os.environ, {envvar: name}): env = get_fake_env() comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) + if isinstance(comp, (mesonbuild.compilers.AppleClangCCompiler, + mesonbuild.compilers.AppleClangCPPCompiler, + mesonbuild.compilers.AppleClangObjCCompiler, + mesonbuild.compilers.AppleClangObjCPPCompiler)): + raise unittest.SkipTest('AppleClang is currently only supported with ld64') if lang != 'rust' and comp.use_linker_args('bfd') == []: raise unittest.SkipTest( 'Compiler {} does not support using alternative linkers'.format(comp.id)) @@ -7349,8 +7360,9 @@ c = ['{0}'] self._check_ld('ld.lld', 'lld', 'c', 'ld.lld') @skip_if_not_language('rust') + @skipIfNoExecutable('ld.gold') # need an additional check here because _check_ld checks for gcc def test_ld_environment_variable_rust(self): - self._check_ld('ld.gold', 'gold', 'rust', 'ld.gold') + self._check_ld('gcc', 'gcc -fuse-ld=gold', 'rust', 'ld.gold') def test_ld_environment_variable_cpp(self): self._check_ld('ld.gold', 'gold', 'cpp', 'ld.gold') diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index f40dc52..f7e0eeb 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -10,6 +10,7 @@ conf.set('BE_TRUE', true) assert(conf.get('var') == 'mystring', 'Get function is not working.') assert(conf.get('var', 'default') == 'mystring', 'Get function is not working.') assert(conf.get('notthere', 'default') == 'default', 'Default value getting is not working.') +assert(conf.keys() == ['BE_TRUE', 'other', 'second', 'var'], 'Keys function is not working') cfile = configure_file(input : 'config.h.in', output : 'config.h', diff --git a/test cases/common/231 external project/libfoo/configure b/test cases/common/231 external project/libfoo/configure index a867b48..0e4aa72 100755 --- a/test cases/common/231 external project/libfoo/configure +++ b/test cases/common/231 external project/libfoo/configure @@ -27,7 +27,7 @@ case $i in esac done -DEP_ARGS=$(pkg-config somelib --cflags --libs) +DEP_ARGS=$(pkg-config --cflags --libs somelib) cat > Makefile << EOL all: libfoo.$LIBEXT diff --git a/test cases/common/28 multiline string/meson.build b/test cases/common/28 multiline string/meson.build index 9cec114..a87d29a 100644 --- a/test cases/common/28 multiline string/meson.build +++ b/test cases/common/28 multiline string/meson.build @@ -26,6 +26,8 @@ endif cc = meson.get_compiler('c') prog = ''' +#include <stdio.h> + int main(void) { int num = 1; printf("%d\n", num); 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 index 4e2856f..0a32674 100644 --- 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 @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/76 link with shared module on osx/meson.build:8:0: ERROR: target links against shared modules." + "line": "test cases/failing/76 link with shared module on osx/meson.build:8:0: ERROR: target links against shared modules. This is not permitted on OSX" } ] } diff --git a/test cases/frameworks/34 gir static lib/meson.build b/test cases/frameworks/34 gir static lib/meson.build new file mode 100644 index 0000000..d7ab9bf --- /dev/null +++ b/test cases/frameworks/34 gir static lib/meson.build @@ -0,0 +1,18 @@ +project('gobject-introspection-static-helper', 'c') + +gir = find_program('g-ir-scanner', required: false) +if not gir.found() + error('MESON_SKIP_TEST g-ir-scanner not found.') +endif + +gobject_introspection = dependency('gobject-introspection-1.0') +# This won't work without https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/72 +if gobject_introspection.version().version_compare('< 1.58.1') + error('MESON_SKIP_TEST gobject-introspection is too old to support static libraries') +endif + +gnome = import('gnome') +gobj = dependency('gobject-2.0') + +subdir('statichelper') +subdir('subdir/gir') diff --git a/test cases/frameworks/34 gir static lib/statichelper/meson-sample.c b/test cases/frameworks/34 gir static lib/statichelper/meson-sample.c new file mode 100644 index 0000000..2ed9cdf --- /dev/null +++ b/test cases/frameworks/34 gir static lib/statichelper/meson-sample.c @@ -0,0 +1,126 @@ +#include "meson-sample.h" + +typedef struct _MesonSamplePrivate +{ + gchar *msg; +} MesonSamplePrivate; + + +G_DEFINE_TYPE_WITH_PRIVATE (MesonSample, meson_sample, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_MSG, + LAST_PROP +}; + +static GParamSpec *gParamSpecs [LAST_PROP]; + +/** + * meson_sample_new: + * @msg: The message to set. + * + * Allocates a new #MesonSample. + * + * Returns: (transfer full): a #MesonSample. + */ +MesonSample * +meson_sample_new (const gchar *msg) +{ + g_return_val_if_fail (msg != NULL, NULL); + + return g_object_new (MESON_TYPE_SAMPLE, + "message", msg, + NULL); +} + +static void +meson_sample_finalize (GObject *object) +{ + MesonSamplePrivate *priv = meson_sample_get_instance_private ((MesonSample *) object); + + g_clear_pointer (&priv->msg, g_free); + + G_OBJECT_CLASS (meson_sample_parent_class)->finalize (object); +} + +static void +meson_sample_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MesonSamplePrivate *priv = meson_sample_get_instance_private ((MesonSample *) object); + + switch (prop_id) + { + case PROP_MSG: + g_value_set_string (value, priv->msg); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +meson_sample_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MesonSamplePrivate *priv = meson_sample_get_instance_private ((MesonSample *) object); + + switch (prop_id) + { + case PROP_MSG: + priv->msg = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +meson_sample_class_init (MesonSampleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meson_sample_finalize; + object_class->get_property = meson_sample_get_property; + object_class->set_property = meson_sample_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_sample_init (MesonSample *self) +{ +} + +/** + * meson_sample_print_message: + * @self: a #MesonSample. + * + * Prints the message. + * + */ +void +meson_sample_print_message (MesonSample *self) +{ + MesonSamplePrivate *priv; + + g_return_if_fail (MESON_IS_SAMPLE (self)); + + priv = meson_sample_get_instance_private (self); + + g_print ("Message: %s\n", priv->msg); +} diff --git a/test cases/frameworks/34 gir static lib/statichelper/meson-sample.h.in b/test cases/frameworks/34 gir static lib/statichelper/meson-sample.h.in new file mode 100644 index 0000000..d0ab29e --- /dev/null +++ b/test cases/frameworks/34 gir static lib/statichelper/meson-sample.h.in @@ -0,0 +1,22 @@ +#ifndef MESON_SAMPLE_H +#define MESON_SAMPLE_H + +#include <@HEADER@> + +G_BEGIN_DECLS + +#define MESON_TYPE_SAMPLE (meson_sample_get_type()) + +G_DECLARE_DERIVABLE_TYPE (MesonSample, meson_sample, MESON, SAMPLE, GObject) + +struct _MesonSampleClass { + GObjectClass parent_class; +}; + + +MesonSample *meson_sample_new (const gchar *msg); +void meson_sample_print_message (MesonSample *self); + +G_END_DECLS + +#endif /* MESON_SAMPLE_H */ diff --git a/test cases/frameworks/34 gir static lib/statichelper/meson.build b/test cases/frameworks/34 gir static lib/statichelper/meson.build new file mode 100644 index 0000000..a815372 --- /dev/null +++ b/test cases/frameworks/34 gir static lib/statichelper/meson.build @@ -0,0 +1,22 @@ +conf = configuration_data() +conf.set('HEADER', 'glib-object.h') + +meson_sample_header = configure_file( + input : 'meson-sample.h.in', + output : 'meson-sample.h', + configuration : conf) + +statichelper_sources = files('meson-sample.c') + [meson_sample_header] + +statichelper_lib = static_library( + 'statichelper', + sources : statichelper_sources, + dependencies : gobj, +) + +statichelper_inc = include_directories('.') +statichelper_dep = declare_dependency( + link_with: statichelper_lib, + dependencies : gobj, + include_directories : [statichelper_inc], +) diff --git a/test cases/frameworks/34 gir static lib/subdir/gir/meson-subsample.c b/test cases/frameworks/34 gir static lib/subdir/gir/meson-subsample.c new file mode 100644 index 0000000..2d58a10 --- /dev/null +++ b/test cases/frameworks/34 gir static lib/subdir/gir/meson-subsample.c @@ -0,0 +1,124 @@ +#include "meson-subsample.h" + +struct _MesonSubSample +{ + MesonSample parent_instance; + + gchar *msg; +}; + +G_DEFINE_TYPE (MesonSubSample, meson_sub_sample, MESON_TYPE_SAMPLE) + +enum { + PROP_0, + PROP_MSG, + LAST_PROP +}; + +static GParamSpec *gParamSpecs [LAST_PROP]; + +/** + * meson_sub_sample_new: + * @msg: The message to set. + * + * Allocates a new #MesonSubSample. + * + * Returns: (transfer full): a #MesonSubSample. + */ +MesonSubSample * +meson_sub_sample_new (const gchar *msg) +{ + g_return_val_if_fail (msg != NULL, NULL); + + return g_object_new (MESON_TYPE_SUB_SAMPLE, + "message", msg, + NULL); +} + +static void +meson_sub_sample_finalize (GObject *object) +{ + MesonSubSample *self = (MesonSubSample *)object; + + g_clear_pointer (&self->msg, g_free); + + G_OBJECT_CLASS (meson_sub_sample_parent_class)->finalize (object); +} + +static void +meson_sub_sample_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MesonSubSample *self = MESON_SUB_SAMPLE (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_sub_sample_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MesonSubSample *self = MESON_SUB_SAMPLE (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_sub_sample_class_init (MesonSubSampleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meson_sub_sample_finalize; + object_class->get_property = meson_sub_sample_get_property; + object_class->set_property = meson_sub_sample_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_sub_sample_init (MesonSubSample *self) +{ +} + +/** + * meson_sub_sample_print_message: + * @self: a #MesonSubSample. + * + * Prints the message. + * + * Returns: Nothing. + */ +void +meson_sub_sample_print_message (MesonSubSample *self) +{ + g_return_if_fail (MESON_IS_SUB_SAMPLE (self)); + + g_print ("Message: %s\n", self->msg); +} diff --git a/test cases/frameworks/34 gir static lib/subdir/gir/meson-subsample.h b/test cases/frameworks/34 gir static lib/subdir/gir/meson-subsample.h new file mode 100644 index 0000000..9d34a08 --- /dev/null +++ b/test cases/frameworks/34 gir static lib/subdir/gir/meson-subsample.h @@ -0,0 +1,17 @@ +#ifndef MESON_SUB_SAMPLE_H +#define MESON_SUB_SAMPLE_H + +#include <glib-object.h> +#include <meson-sample.h> + +G_BEGIN_DECLS + +#define MESON_TYPE_SUB_SAMPLE (meson_sub_sample_get_type()) + +G_DECLARE_FINAL_TYPE (MesonSubSample, meson_sub_sample, MESON, SUB_SAMPLE, MesonSample) + +MesonSubSample *meson_sub_sample_new (const gchar *msg); + +G_END_DECLS + +#endif /* MESON_SUB_SAMPLE_H */ diff --git a/test cases/frameworks/34 gir static lib/subdir/gir/meson.build b/test cases/frameworks/34 gir static lib/subdir/gir/meson.build new file mode 100644 index 0000000..b26f04e --- /dev/null +++ b/test cases/frameworks/34 gir static lib/subdir/gir/meson.build @@ -0,0 +1,28 @@ +libsources = ['meson-subsample.c', 'meson-subsample.h'] + +girlib = shared_library( + 'girlib', + sources : libsources, + dependencies : [gobj, statichelper_dep], + install : true +) + +girexe = executable( + 'girprog', + sources : 'prog.c', + dependencies : [gobj, statichelper_dep], + link_with : girlib +) + +gnome.generate_gir( + girlib, statichelper_lib, + sources : [ libsources, statichelper_sources ], + nsversion : '1.0', + namespace : 'Meson', + symbol_prefix : 'meson_', + identifier_prefix : 'Meson', + includes : ['GObject-2.0'], + install : true +) + +test('gobject introspection/subproject/c', girexe) diff --git a/test cases/frameworks/34 gir static lib/subdir/gir/prog.c b/test cases/frameworks/34 gir static lib/subdir/gir/prog.c new file mode 100644 index 0000000..f25c9d8 --- /dev/null +++ b/test cases/frameworks/34 gir static lib/subdir/gir/prog.c @@ -0,0 +1,12 @@ +#include "meson-subsample.h" + +gint +main (gint argc, + gchar *argv[]) +{ + MesonSample * i = (MesonSample*) meson_sub_sample_new ("Hello, sub/meson/c!"); + meson_sample_print_message (i); + g_object_unref (i); + + return 0; +} diff --git a/test cases/frameworks/34 gir static lib/test.json b/test cases/frameworks/34 gir static lib/test.json new file mode 100644 index 0000000..a99e5a7 --- /dev/null +++ b/test cases/frameworks/34 gir static lib/test.json @@ -0,0 +1,8 @@ +{ + "installed": [ + {"type": "file", "file": "usr/lib/girepository-1.0/Meson-1.0.typelib"}, + {"type": "expr", "file": "usr/lib/?libgirlib.so"}, + {"type": "file", "platform": "cygwin", "file": "usr/lib/libgirlib.dll.a"}, + {"type": "file", "file": "usr/share/gir-1.0/Meson-1.0.gir"} + ] +} diff --git a/test cases/frameworks/7 gnome/mkenums/enums.c.in b/test cases/frameworks/7 gnome/mkenums/enums.c.in index 62e1adc..1c19d8f 100644 --- a/test cases/frameworks/7 gnome/mkenums/enums.c.in +++ b/test cases/frameworks/7 gnome/mkenums/enums.c.in @@ -13,9 +13,9 @@ /*** BEGIN value-header ***/ GType @enum_name@_get_type(void) { - static volatile gsize g_define_type_id__volatile = 0; + static gsize static_g_define_type_id = 0; - if(g_once_init_enter(&g_define_type_id__volatile)) { + if(g_once_init_enter(&static_g_define_type_id)) { static const G@Type@Value values [] = { /*** END value-header ***/ @@ -29,10 +29,10 @@ GType GType g_define_type_id = g_@type@_register_static(g_intern_static_string("@EnumName@"), values); - g_once_init_leave(&g_define_type_id__volatile, g_define_type_id); + g_once_init_leave(&static_g_define_type_id, g_define_type_id); } - return g_define_type_id__volatile; + return static_g_define_type_id; } /*** END value-tail ***/ diff --git a/test cases/frameworks/7 gnome/mkenums/enums2.c.in b/test cases/frameworks/7 gnome/mkenums/enums2.c.in index 62e1adc..1c19d8f 100644 --- a/test cases/frameworks/7 gnome/mkenums/enums2.c.in +++ b/test cases/frameworks/7 gnome/mkenums/enums2.c.in @@ -13,9 +13,9 @@ /*** BEGIN value-header ***/ GType @enum_name@_get_type(void) { - static volatile gsize g_define_type_id__volatile = 0; + static gsize static_g_define_type_id = 0; - if(g_once_init_enter(&g_define_type_id__volatile)) { + if(g_once_init_enter(&static_g_define_type_id)) { static const G@Type@Value values [] = { /*** END value-header ***/ @@ -29,10 +29,10 @@ GType GType g_define_type_id = g_@type@_register_static(g_intern_static_string("@EnumName@"), values); - g_once_init_leave(&g_define_type_id__volatile, g_define_type_id); + g_once_init_leave(&static_g_define_type_id, g_define_type_id); } - return g_define_type_id__volatile; + return static_g_define_type_id; } /*** END value-tail ***/ diff --git a/test cases/frameworks/7 gnome/mkenums/meson.build b/test cases/frameworks/7 gnome/mkenums/meson.build index 3d7adf0..8ff05ba 100644 --- a/test cases/frameworks/7 gnome/mkenums/meson.build +++ b/test cases/frameworks/7 gnome/mkenums/meson.build @@ -89,9 +89,9 @@ enums_c3 = gnome.mkenums('enums3.c', vhead : ''' GType @enum_name@_get_type(void) { - static volatile gsize g_define_type_id__volatile = 0; + static gsize static_g_define_type_id = 0; - if(g_once_init_enter(&g_define_type_id__volatile)) { + if(g_once_init_enter(&static_g_define_type_id)) { static const G@Type@Value values [] = { ''', vprod : ''' { @VALUENAME@, "@VALUENAME@", "@valuenick@" },''', @@ -100,10 +100,10 @@ GType GType g_define_type_id = g_@type@_register_static(g_intern_static_string("@EnumName@"), values); - g_once_init_leave(&g_define_type_id__volatile, g_define_type_id); + g_once_init_leave(&static_g_define_type_id, g_define_type_id); } - return g_define_type_id__volatile; + return static_g_define_type_id; } ''') diff --git a/test cases/frameworks/8 flex/lexer.l b/test cases/frameworks/8 flex/lexer.l index 85293b2..952eeea 100644 --- a/test cases/frameworks/8 flex/lexer.l +++ b/test cases/frameworks/8 flex/lexer.l @@ -1,6 +1,9 @@ %{ #include <stdlib.h> #include "parser.tab.h" + +extern int yylex(void); +extern int yyerror(); %} %% diff --git a/test cases/frameworks/8 flex/parser.y b/test cases/frameworks/8 flex/parser.y index ff8754f..663f2f3 100644 --- a/test cases/frameworks/8 flex/parser.y +++ b/test cases/frameworks/8 flex/parser.y @@ -1,3 +1,8 @@ +%{ +extern int yylex(void); +extern int yyerror(); +%} + %token BOOLEAN %% diff --git a/test cases/rust/2 sharedlib/meson.build b/test cases/rust/2 sharedlib/meson.build index 42f6e23..aac2968 100644 --- a/test cases/rust/2 sharedlib/meson.build +++ b/test cases/rust/2 sharedlib/meson.build @@ -1,5 +1,9 @@ project('rust shared library', 'rust') +if host_machine.system() == 'darwin' + error('MESON_SKIP_TEST: doesnt work right on macos, please fix!') +endif + l = shared_library('stuff', 'stuff.rs', install : true) e = executable('prog', 'prog.rs', link_with : l, install : true) diff --git a/test cases/rust/4 polyglot/meson.build b/test cases/rust/4 polyglot/meson.build index b7eef57..4955516 100644 --- a/test cases/rust/4 polyglot/meson.build +++ b/test cases/rust/4 polyglot/meson.build @@ -1,5 +1,9 @@ project('rust and c polyglot executable', 'c', 'rust') +if host_machine.system() == 'darwin' + error('MESON_SKIP_TEST: doesnt work right on macos, please fix!') +endif + l = library('stuff', 'stuff.rs', rust_crate_type: 'cdylib', install : true) e = executable('prog', 'prog.c', link_with : l, install : true) test('polyglottest', e) diff --git a/test cases/unit/59 introspect buildoptions/meson.build b/test cases/unit/59 introspect buildoptions/meson.build index 16df028..9cfdc2c 100644 --- a/test cases/unit/59 introspect buildoptions/meson.build +++ b/test cases/unit/59 introspect buildoptions/meson.build @@ -1,4 +1,4 @@ -project('introspect buildargs', ['c'], default_options: ['c_std=c11', 'cpp_std=c++14', 'buildtype=release']) +project('introspect buildargs', ['c'], default_options: ['c_std=c99', 'cpp_std=c++14', 'buildtype=release']) subA = subproject('projectA') diff --git a/test cases/vala/8 generated sources/dependency-generated/enum-types.c.template b/test cases/vala/8 generated sources/dependency-generated/enum-types.c.template index 5ecdd2d..85d74d1 100644 --- a/test cases/vala/8 generated sources/dependency-generated/enum-types.c.template +++ b/test cases/vala/8 generated sources/dependency-generated/enum-types.c.template @@ -14,9 +14,9 @@ GType @enum_name@_get_type (void) { - static volatile gsize g_define_type_id__volatile = 0; + static gsize static_g_define_type_id = 0; - if (g_once_init_enter (&g_define_type_id__volatile)) { + if (g_once_init_enter (&static_g_define_type_id)) { static const G@Type@Value values[] = { /*** END value-header ***/ @@ -30,10 +30,10 @@ GType GType g_define_type_id = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); - g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + g_once_init_leave (&static_g_define_type_id, g_define_type_id); } - return g_define_type_id__volatile; + return static_g_define_type_id; } /*** END value-tail ***/ |